mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-10-17 11:43:54 +00:00
* fix(#11530): markdown viewer enableSeeMoreVariant is not working as expected * add unit test * remove unused component * update the comment
This commit is contained in:
parent
f22d604c54
commit
1da5dd9b9b
@ -40,10 +40,6 @@ jest.mock('../common/ServiceDocPanel/ServiceDocPanel', () => {
|
||||
return jest.fn().mockReturnValue(<div>ServiceDocPanel</div>);
|
||||
});
|
||||
|
||||
jest.mock('../common/ServiceRightPanel/ServiceRightPanel', () => {
|
||||
return jest.fn().mockReturnValue(<div>Right Panel</div>);
|
||||
});
|
||||
|
||||
jest.mock('components/common/ResizablePanels/ResizablePanels', () =>
|
||||
jest.fn().mockImplementation(({ firstPanel, secondPanel }) => (
|
||||
<>
|
||||
|
@ -33,7 +33,6 @@ const ChangeLogs = ({ data }: Props) => {
|
||||
<RichTextEditorPreviewer
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={data[log]}
|
||||
maxLength={data[log].length}
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
@ -132,7 +132,6 @@ const ServiceDocPanel: FC<ServiceDocPanelProp> = ({
|
||||
<RichTextEditorPreviewer
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={markdownContent}
|
||||
maxLength={markdownContent.length}
|
||||
/>
|
||||
</Col>
|
||||
</Row>
|
||||
|
@ -1,34 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { ServiceCategory } from 'enums/service.enum';
|
||||
import { PipelineType } from 'generated/api/services/ingestionPipelines/createIngestionPipeline';
|
||||
|
||||
export type ExcludedPipelineType = Exclude<
|
||||
PipelineType,
|
||||
| PipelineType.DataInsight
|
||||
| PipelineType.ElasticSearchReindex
|
||||
| PipelineType.TestSuite
|
||||
>;
|
||||
|
||||
export interface RightPanelProps {
|
||||
activeStep: number;
|
||||
isIngestion: boolean;
|
||||
serviceName: string;
|
||||
isUpdating: boolean;
|
||||
selectedService: string;
|
||||
selectedServiceCategory: ServiceCategory;
|
||||
showDeployedTitle?: boolean;
|
||||
pipelineType?: ExcludedPipelineType;
|
||||
ingestionName?: string;
|
||||
activeField?: string;
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { act, render, screen } from '@testing-library/react';
|
||||
import { ServiceCategory } from 'enums/service.enum';
|
||||
import { PipelineType } from 'generated/api/services/ingestionPipelines/createIngestionPipeline';
|
||||
import React from 'react';
|
||||
import RightPanel from './ServiceRightPanel';
|
||||
import { ExcludedPipelineType } from './ServiceRightPanel.interface';
|
||||
|
||||
jest.mock('components/common/rich-text-editor/RichTextEditorPreviewer', () =>
|
||||
jest
|
||||
.fn()
|
||||
.mockImplementation(({ markdown }) => (
|
||||
<div data-testid="requirement-text">{markdown}</div>
|
||||
))
|
||||
);
|
||||
|
||||
jest.mock('rest/miscAPI', () => ({
|
||||
fetchMarkdownFile: jest
|
||||
.fn()
|
||||
.mockImplementation(() => Promise.resolve('markdown text')),
|
||||
}));
|
||||
|
||||
const mockProps = {
|
||||
isIngestion: false,
|
||||
pipelineType: PipelineType.Metadata as ExcludedPipelineType,
|
||||
activeStep: 1,
|
||||
isAirflowRunning: true,
|
||||
showDeployedTitle: true,
|
||||
isUpdating: false,
|
||||
ingestionName: 'service_ingestion',
|
||||
serviceName: 'service',
|
||||
activeField: 'root_username',
|
||||
selectedServiceCategory: ServiceCategory.DATABASE_SERVICES,
|
||||
selectedService: 'Mysql',
|
||||
};
|
||||
|
||||
describe('Right Panel Component', () => {
|
||||
it('Should render the active field doc', async () => {
|
||||
await act(async () => {
|
||||
render(<RightPanel {...mockProps} />);
|
||||
});
|
||||
|
||||
const activeFieldName = screen.getByTestId('active-field-name');
|
||||
|
||||
expect(activeFieldName).toBeInTheDocument();
|
||||
|
||||
expect(activeFieldName).toHaveTextContent('Username');
|
||||
|
||||
const activeFieldDocumentElement = screen.getByTestId('requirement-text');
|
||||
|
||||
expect(activeFieldDocumentElement).toBeInTheDocument();
|
||||
|
||||
expect(activeFieldDocumentElement).toHaveTextContent('markdown text');
|
||||
});
|
||||
|
||||
it('Should render the current step guide if active field is empty', async () => {
|
||||
await act(async () => {
|
||||
render(<RightPanel {...mockProps} activeField={undefined} />);
|
||||
});
|
||||
|
||||
expect(screen.queryByTestId('active-field-name')).not.toBeInTheDocument();
|
||||
|
||||
const activeFieldDocumentElement = screen.queryByTestId('requirement-text');
|
||||
|
||||
expect(activeFieldDocumentElement).not.toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('label.add-a-new-service')).toBeInTheDocument();
|
||||
|
||||
expect(
|
||||
screen.getByText('message.add-new-service-description')
|
||||
).toBeInTheDocument();
|
||||
});
|
||||
});
|
@ -1,219 +0,0 @@
|
||||
/*
|
||||
* Copyright 2023 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 { Affix, Card } from 'antd';
|
||||
import RichTextEditorPreviewer from 'components/common/rich-text-editor/RichTextEditorPreviewer';
|
||||
import Loader from 'components/Loader/Loader';
|
||||
import { oneofOrEndsWithNumberRegex } from 'constants/regex.constants';
|
||||
import {
|
||||
addServiceGuide,
|
||||
addServiceGuideWOAirflow,
|
||||
EMAIL_CONFIG_SERVICE_CATEGORY,
|
||||
} from 'constants/service-guide.constant';
|
||||
import { INGESTION_GUIDE_MAP } from 'constants/Services.constant';
|
||||
import { useAirflowStatus } from 'hooks/useAirflowStatus';
|
||||
import { first, last, startCase } from 'lodash';
|
||||
import React, { FC, useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { fetchMarkdownFile } from 'rest/miscAPI';
|
||||
import { SupportedLocales } from 'utils/i18next/i18nextUtil';
|
||||
import { getEmailConfigStepGuide } from 'utils/ServiceRightPanelUtils';
|
||||
import { getFormattedGuideText, getServiceType } from 'utils/ServiceUtils';
|
||||
import { RightPanelProps } from './ServiceRightPanel.interface';
|
||||
|
||||
const RightPanel: FC<RightPanelProps> = ({
|
||||
isIngestion,
|
||||
pipelineType,
|
||||
activeStep,
|
||||
isUpdating,
|
||||
ingestionName,
|
||||
serviceName,
|
||||
activeField,
|
||||
selectedServiceCategory,
|
||||
selectedService,
|
||||
showDeployedTitle = false,
|
||||
}) => {
|
||||
const panelContainerRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
const { isAirflowAvailable } = useAirflowStatus();
|
||||
const { t, i18n } = useTranslation();
|
||||
|
||||
const [activeFieldDocument, setActiveFieldDocument] = useState<string>('');
|
||||
const [isLoading, setIsLoading] = useState<boolean>(false);
|
||||
|
||||
const isEmailConfigPage = useMemo(
|
||||
() => selectedService === EMAIL_CONFIG_SERVICE_CATEGORY,
|
||||
[selectedService]
|
||||
);
|
||||
|
||||
const activeStepGuide = useMemo(() => {
|
||||
let guideTemp;
|
||||
|
||||
if (isIngestion && pipelineType) {
|
||||
guideTemp = INGESTION_GUIDE_MAP[pipelineType]?.find(
|
||||
(item) => item.step === activeStep
|
||||
);
|
||||
} else {
|
||||
guideTemp =
|
||||
!isAirflowAvailable && activeStep === 4
|
||||
? addServiceGuideWOAirflow
|
||||
: addServiceGuide.find((item) => item.step === activeStep);
|
||||
}
|
||||
|
||||
return guideTemp;
|
||||
}, [isIngestion, pipelineType, isAirflowAvailable, activeStep]);
|
||||
|
||||
const activeFieldName = useMemo(() => {
|
||||
/**
|
||||
* active field is like root_fieldName
|
||||
* so we need to split and get the fieldName
|
||||
*/
|
||||
const fieldNameArr = activeField?.split('/');
|
||||
|
||||
const fieldName = last(fieldNameArr);
|
||||
|
||||
// check if activeField is select or list field
|
||||
if (oneofOrEndsWithNumberRegex.test(fieldName ?? '')) {
|
||||
return first(fieldName?.split('_'));
|
||||
} else {
|
||||
return fieldName;
|
||||
}
|
||||
}, [activeField]);
|
||||
|
||||
const showActiveFieldElement = Boolean(
|
||||
activeFieldName && activeFieldDocument
|
||||
);
|
||||
|
||||
const getActiveStepTitle = (title: string) => {
|
||||
const deployMessage = showDeployedTitle ? ` & ${t('label.deployed')}` : '';
|
||||
const updateTitle = title.replace(
|
||||
t('label.added'),
|
||||
`${t('label.updated')}${deployMessage}`
|
||||
);
|
||||
const newTitle = showDeployedTitle
|
||||
? title.replace(t('label.added'), `${t('label.added')}${deployMessage}`)
|
||||
: title;
|
||||
|
||||
return isUpdating ? updateTitle : newTitle;
|
||||
};
|
||||
|
||||
const getActiveStepDescription = (description: string) => {
|
||||
const replaceText = isIngestion
|
||||
? `<${t('label.ingestion-pipeline-name')}>`
|
||||
: `<${t('label.service-name')}>`;
|
||||
|
||||
const replacement = isIngestion ? ingestionName || '' : serviceName;
|
||||
|
||||
return getFormattedGuideText(description, replaceText, replacement);
|
||||
};
|
||||
|
||||
const fetchFieldDocument = async () => {
|
||||
const serviceType = isEmailConfigPage
|
||||
? selectedServiceCategory
|
||||
: getServiceType(selectedServiceCategory);
|
||||
setIsLoading(true);
|
||||
try {
|
||||
let response = '';
|
||||
const isEnglishLanguage = i18n.language === SupportedLocales.English;
|
||||
const filePath = `${i18n.language}/${serviceType}/${selectedService}/fields/${activeFieldName}.md`;
|
||||
const fallbackFilePath = `${SupportedLocales.English}/${serviceType}/${selectedService}/fields/${activeFieldName}.md`;
|
||||
|
||||
const [translation, fallbackTranslation] = await Promise.allSettled([
|
||||
fetchMarkdownFile(filePath),
|
||||
isEnglishLanguage
|
||||
? Promise.reject('')
|
||||
: fetchMarkdownFile(fallbackFilePath),
|
||||
]);
|
||||
|
||||
if (translation.status === 'fulfilled') {
|
||||
response = translation.value;
|
||||
}
|
||||
|
||||
if (isEnglishLanguage && fallbackTranslation.status === 'fulfilled') {
|
||||
response = fallbackTranslation.value;
|
||||
}
|
||||
|
||||
setActiveFieldDocument(response);
|
||||
} catch (error) {
|
||||
setActiveFieldDocument('');
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleAffixTarget = () => document.getElementById('page-container-v1');
|
||||
|
||||
useEffect(() => {
|
||||
const shouldFetchFieldDoc = Boolean(
|
||||
selectedService && selectedServiceCategory && activeFieldName
|
||||
);
|
||||
// only fetch file when required fields are present
|
||||
if (shouldFetchFieldDoc) {
|
||||
fetchFieldDocument();
|
||||
}
|
||||
}, [
|
||||
selectedService,
|
||||
selectedServiceCategory,
|
||||
activeFieldName,
|
||||
isEmailConfigPage,
|
||||
]);
|
||||
|
||||
const activeStepGuideElement = activeStepGuide ? (
|
||||
<>
|
||||
<h6 className="tw-heading tw-text-base">
|
||||
{getActiveStepTitle(activeStepGuide.title)}
|
||||
</h6>
|
||||
<div className="tw-mb-5" data-test="current-step-guide">
|
||||
{getActiveStepDescription(activeStepGuide.description)}
|
||||
</div>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const activeFieldDocumentElement = activeFieldName ? (
|
||||
<>
|
||||
<h6 className="tw-heading tw-text-base" data-testid="active-field-name">
|
||||
{startCase(activeFieldName)}
|
||||
</h6>
|
||||
<RichTextEditorPreviewer
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={activeFieldDocument}
|
||||
maxLength={activeFieldDocument.length}
|
||||
/>
|
||||
</>
|
||||
) : null;
|
||||
|
||||
const renderElement = useMemo(() => {
|
||||
if (showActiveFieldElement) {
|
||||
return activeFieldDocumentElement;
|
||||
} else {
|
||||
return isEmailConfigPage
|
||||
? getEmailConfigStepGuide()
|
||||
: activeStepGuideElement;
|
||||
}
|
||||
}, [
|
||||
showActiveFieldElement,
|
||||
activeFieldDocumentElement,
|
||||
activeStepGuideElement,
|
||||
selectedService,
|
||||
isEmailConfigPage,
|
||||
]);
|
||||
|
||||
return (
|
||||
<div id="service-right-panel" ref={panelContainerRef}>
|
||||
<Affix offsetTop={5} target={handleAffixTarget}>
|
||||
<Card>{isLoading ? <Loader /> : renderElement}</Card>
|
||||
</Affix>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default RightPanel;
|
@ -11,22 +11,28 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { findByTestId, fireEvent, render } from '@testing-library/react';
|
||||
import {
|
||||
act,
|
||||
findByTestId,
|
||||
fireEvent,
|
||||
render,
|
||||
screen,
|
||||
} from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
import { act } from 'react-test-renderer';
|
||||
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { PreviewerProp } from './RichTextEditor.interface';
|
||||
import RichTextEditorPreviewer from './RichTextEditorPreviewer';
|
||||
|
||||
const mockDescription =
|
||||
// eslint-disable-next-line max-len
|
||||
'**Headings**\n\n# H1\n## H2\n### H3\n\n***\n**Bold**\n\n**bold text**\n\n\n***\n**Italic**\n\n*italic*\n\n***\n**BlockQuote**\n\n> blockquote\n\n***\n**Ordered List**\n\n1. First item\n2. Second item\n3. Third item\n\n\n***\n**Unordered List**\n\n- First item\n- Second item\n- Third item\n\n\n***\n**Code**\n\n`code`\n\n\n***\n**Horizontal Rule**\n\n---\n\n\n***\n**Link**\n[title](https://www.example.com)\n\n\n***\n**Image**\n\n\n\n\n***\n**Table**\n\n| Syntax | Description |\n| ----------- | ----------- |\n| Header | Title |\n| Paragraph | Text |\n***\n\n**Fenced Code Block**\n\n```\n{\n "firstName": "John",\n "lastName": "Smith",\n "age": 25\n}\n```\n\n\n***\n**Strikethrough**\n~~The world is flat.~~\n';
|
||||
|
||||
const mockProp = {
|
||||
const mockProp: PreviewerProp = {
|
||||
markdown: mockDescription,
|
||||
className: '',
|
||||
blurClasses: 'see-more-blur',
|
||||
maxHtClass: 'tw-h-24',
|
||||
maxLen: 300,
|
||||
maxLength: 300,
|
||||
enableSeeMoreVariant: true,
|
||||
};
|
||||
|
||||
@ -344,4 +350,89 @@ describe('Test RichTextEditor Previewer Component', () => {
|
||||
|
||||
expect(markdownParser).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render read more button if enableSeeMoreVariant is true and max length is less than content length', () => {
|
||||
render(<RichTextEditorPreviewer {...mockProp} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('read-more-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Read more toggling should work', async () => {
|
||||
render(<RichTextEditorPreviewer {...mockProp} />, {
|
||||
wrapper: MemoryRouter,
|
||||
});
|
||||
|
||||
const readMoreButton = screen.getByTestId('read-more-button');
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(readMoreButton);
|
||||
});
|
||||
|
||||
const readLessButton = screen.getByTestId('read-less-button');
|
||||
|
||||
expect(readLessButton).toBeInTheDocument();
|
||||
|
||||
await act(async () => {
|
||||
userEvent.click(readLessButton);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('read-more-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should render the whole content if enableSeeMoreVariant is false', () => {
|
||||
const markdown = 'This is a simple paragraph text';
|
||||
|
||||
render(
|
||||
<RichTextEditorPreviewer
|
||||
{...mockProp}
|
||||
enableSeeMoreVariant={false}
|
||||
markdown={markdown}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
expect(screen.getByText(markdown)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('read-more-button')).toBeNull();
|
||||
});
|
||||
|
||||
it('Should render the clipped content if enableSeeMoreVariant is true', () => {
|
||||
const markdown = 'This is a simple paragraph text';
|
||||
|
||||
render(
|
||||
<RichTextEditorPreviewer
|
||||
{...mockProp}
|
||||
enableSeeMoreVariant
|
||||
markdown={markdown}
|
||||
maxLength={20}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
expect(screen.getByText('This is a simple...')).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('read-more-button')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Should not clipped content if enableSeeMoreVariant is true and markdown length is less than max length', () => {
|
||||
const markdown = 'This is a simple paragraph text';
|
||||
|
||||
render(
|
||||
<RichTextEditorPreviewer
|
||||
{...mockProp}
|
||||
enableSeeMoreVariant
|
||||
markdown={markdown}
|
||||
/>,
|
||||
{
|
||||
wrapper: MemoryRouter,
|
||||
}
|
||||
);
|
||||
|
||||
expect(screen.getByText(markdown)).toBeInTheDocument();
|
||||
expect(screen.queryByTestId('read-more-button')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
@ -15,7 +15,7 @@ import { Viewer } from '@toast-ui/react-editor';
|
||||
import { Button } from 'antd';
|
||||
import classNames from 'classnames';
|
||||
import { uniqueId } from 'lodash';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { getTrimmedContent } from 'utils/CommonUtils';
|
||||
import { DESCRIPTION_MAX_PREVIEW_CHARACTERS } from '../../../constants/constants';
|
||||
@ -32,13 +32,30 @@ const RichTextEditorPreviewer = ({
|
||||
}: PreviewerProp) => {
|
||||
const { t } = useTranslation();
|
||||
const [content, setContent] = useState<string>('');
|
||||
const [hideReadMoreText, setHideReadMoreText] = useState<boolean>(
|
||||
markdown.length <= maxLength
|
||||
|
||||
// initially read more will be false
|
||||
const [readMore, setReadMore] = useState<boolean>(false);
|
||||
|
||||
// read more toggle handler
|
||||
const handleReadMoreToggle = () => setReadMore((pre) => !pre);
|
||||
|
||||
// whether has read more content or not
|
||||
const hasReadMore = useMemo(
|
||||
() => enableSeeMoreVariant && markdown.length > maxLength,
|
||||
[enableSeeMoreVariant, markdown, maxLength]
|
||||
);
|
||||
|
||||
const displayMoreHandler = () => {
|
||||
setHideReadMoreText((pre) => !pre);
|
||||
};
|
||||
/**
|
||||
* if hasReadMore is true then value will be based on read more state
|
||||
* else value will be content
|
||||
*/
|
||||
const viewerValue = useMemo(() => {
|
||||
if (hasReadMore) {
|
||||
return readMore ? content : `${getTrimmedContent(content, maxLength)}...`;
|
||||
}
|
||||
|
||||
return content;
|
||||
}, [hasReadMore, readMore, maxLength, content]);
|
||||
|
||||
useEffect(() => {
|
||||
setContent(markdown);
|
||||
@ -83,22 +100,18 @@ const RichTextEditorPreviewer = ({
|
||||
<Viewer
|
||||
extendedAutolinks
|
||||
customHTMLRenderer={customHTMLRenderer}
|
||||
initialValue={
|
||||
hideReadMoreText
|
||||
? content
|
||||
: `${getTrimmedContent(content, maxLength)}...`
|
||||
}
|
||||
initialValue={viewerValue}
|
||||
key={uniqueId()}
|
||||
linkAttributes={{ target: '_blank' }}
|
||||
/>
|
||||
</div>
|
||||
{enableSeeMoreVariant && markdown.length > maxLength && (
|
||||
{hasReadMore && (
|
||||
<Button
|
||||
className="leading-0"
|
||||
data-testid="read-more-button"
|
||||
data-testid={`read-${readMore ? 'less' : 'more'}-button`}
|
||||
type="link"
|
||||
onClick={displayMoreHandler}>
|
||||
{hideReadMoreText
|
||||
onClick={handleReadMoreToggle}>
|
||||
{readMore
|
||||
? t('label.read-type-lowercase', {
|
||||
type: t('label.less-lowercase'),
|
||||
})
|
||||
|
Loading…
x
Reference in New Issue
Block a user