diff --git a/app/api/entities/routes.js b/app/api/entities/routes.js index 5825011db7a..3c91caa76b2 100644 --- a/app/api/entities/routes.js +++ b/app/api/entities/routes.js @@ -163,13 +163,14 @@ export default app => { _id: { type: 'string' }, withPdf: { type: 'string' }, omitRelationships: { type: 'boolean' }, + documentsFullText: { type: 'boolean' }, include: { type: 'array', items: { type: 'string', enum: ['permissions'] } }, }, }, }, }), (req, res, next) => { - const { omitRelationships, include = [], ...query } = req.query; + const { omitRelationships, include = [], documentsFullText, ...query } = req.query; const action = omitRelationships ? 'get' : 'getWithRelationships'; const published = req.user ? {} : { published: true }; const language = req.language ? { language: req.language } : {}; @@ -178,6 +179,7 @@ export default app => { include.map(field => `+${field}`).join(' '), { limit: 1, + documentsFullText, } ) .then(_entities => { diff --git a/app/api/entities/specs/routes.spec.ts b/app/api/entities/specs/routes.spec.ts index 95afa1940e4..76ba6bfe2fe 100644 --- a/app/api/entities/specs/routes.spec.ts +++ b/app/api/entities/specs/routes.spec.ts @@ -68,6 +68,15 @@ describe('entities routes', () => { ]); }); }); + + it('should return documents with fullText when requested', async () => { + const response: SuperTestResponse = await request(app) + .get('/api/entities') + .query({ sharedId: 'shared', documentsFullText: true }); + + expect(response.status).toBe(200); + expect(response.body.rows[0].documents[0].fullText).toBeDefined(); + }); }); describe('POST', () => { diff --git a/app/react/V2/Components/Metadata/index.ts b/app/react/V2/Components/Metadata/index.ts index d8e430b5e1d..20c49a51f85 100644 --- a/app/react/V2/Components/Metadata/index.ts +++ b/app/react/V2/Components/Metadata/index.ts @@ -1 +1,2 @@ export { MetadataDisplay } from './MetadataDisplay'; +export { TemplateLabel } from './TemplateLabel'; diff --git a/app/react/V2/Components/PDFViewer/PDF.tsx b/app/react/V2/Components/PDFViewer/PDF.tsx index 78999e62191..742053f77ca 100644 --- a/app/react/V2/Components/PDFViewer/PDF.tsx +++ b/app/react/V2/Components/PDFViewer/PDF.tsx @@ -41,13 +41,16 @@ const PDF = ({ const pdfContainerRef = useRef(null); const [pdf, setPDF] = useState(); const [error, setError] = useState(); + const [containerWidth, setContainerWidth] = useState(undefined); + const resizeTimeoutRef = useRef(); + const padding = 10; const containerStyles = { height: size?.height || '100%', width: size?.width || '100%', overflow: size?.overflow || 'auto', - paddingLeft: '10px', - paddingRight: '10px', + paddingLeft: `${padding}px`, + paddingRight: `${padding}px`, }; useEffect(() => { @@ -72,6 +75,40 @@ const PDF = ({ }; }, [scrollToPage, pdf]); + useEffect(() => { + const container = pdfContainerRef.current; + + if (!container) { + return undefined; + } + + const initialWidth = container.offsetWidth - padding * 2; + setContainerWidth(initialWidth); + + const resizeObserver = new ResizeObserver(entries => { + const [entry] = entries; + if (entry && entry.contentRect) { + if (resizeTimeoutRef.current) { + clearTimeout(resizeTimeoutRef.current); + } + + resizeTimeoutRef.current = setTimeout(() => { + const newWidth = entry.contentRect.width - padding * 2; + setContainerWidth(newWidth); + }, 150); + } + }); + + resizeObserver.observe(container); + + return () => { + if (resizeTimeoutRef.current) { + clearTimeout(resizeTimeoutRef.current); + } + resizeObserver.disconnect(); + }; + }, []); + if (error) { return
{error}
; } @@ -84,8 +121,6 @@ const PDF = ({ const regionId = number.toString(); const pageHighlights = highlights ? highlights[regionId] : undefined; const shouldScrollToPage = scrollToPage === regionId; - const containerWidth = - pdfContainerRef.current?.offsetWidth && pdfContainerRef.current.offsetWidth - 20; return (
(); const [pdfScale, setPdfScale] = useAtom(pdfScaleAtom); const pageContainerRef = useRef(null); + const pageViewerRef = useRef(null); + const pdfPageRef = useRef(null); useEffect(() => { const currentContainer = pageContainerRef.current; @@ -30,6 +32,8 @@ const PDFPage = ({ pdf, page, eventBus, containerWidth, highlights }: PDFPagePro .getPage(page) .then(pdfPage => { if (currentContainer && pdfPage) { + pdfPageRef.current = pdfPage; + const originalViewport = pdfPage.getViewport({ scale: 1 }); const scale = calculateScaling( originalViewport.width * PDFJS.PixelsPerInch.PDF_TO_CSS_UNITS, @@ -49,6 +53,7 @@ const PDFPage = ({ pdf, page, eventBus, containerWidth, highlights }: PDFPagePro }); pageViewer.setPdfPage(pdfPage); + pageViewerRef.current = pageViewer; const handleIntersection: IntersectionObserverCallback = entries => { const [entry] = entries; @@ -81,12 +86,40 @@ const PDFPage = ({ pdf, page, eventBus, containerWidth, highlights }: PDFPagePro }); return () => { - if (currentContainer) observer.unobserve(currentContainer); + if (currentContainer && observer) observer.unobserve(currentContainer); + if (pageViewerRef.current) { + pageViewerRef.current.destroy(); + } }; //pdf rendering is expensive and we want to make sure there's a single effect that runs only on mount // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + useEffect(() => { + const pageViewer = pageViewerRef.current; + const pdfPage = pdfPageRef.current; + + if (!pageViewer || !pdfPage || !containerWidth) return; + + const originalViewport = pdfPage.getViewport({ scale: 1 }); + const newScale = calculateScaling( + originalViewport.width * PDFJS.PixelsPerInch.PDF_TO_CSS_UNITS, + containerWidth + ); + + if (Math.abs(pageViewer.scale - newScale) > 0.01) { + setPdfScale(newScale); + + pageViewer.update({ scale: newScale }); + + if (pageViewer.renderingState === PDFJSViewer.RenderingStates.FINISHED) { + pageViewer.draw().catch((e: Error) => { + setError(e.message); + }); + } + } + }, [containerWidth, setPdfScale]); + if (error) { return
{error}
; } diff --git a/app/react/V2/Components/PDFViewer/PlainText.tsx b/app/react/V2/Components/PDFViewer/PlainText.tsx new file mode 100644 index 00000000000..98034f06a5a --- /dev/null +++ b/app/react/V2/Components/PDFViewer/PlainText.tsx @@ -0,0 +1,30 @@ +import React from 'react'; +import { FileType } from 'shared/types/fileType'; + +interface PlainTextProps { + file?: FileType; + page?: number | string; + className?: string; + dir?: 'ltr' | 'rtl'; +} + +export const PlainText = ({ className = '', dir, page, file }: PlainTextProps) => { + const firstPage = file?.fullText + ? Object.keys(file.fullText) + .map(k => Number(k)) + .filter(n => !Number.isNaN(n)) + .sort((a, b) => a - b)[0] + : 1; + + const pageKey = page ? String(page) : String(firstPage); + + const pageText = file?.fullText?.[pageKey] ?? ''; + + return ( +
+ {pageText} +
+ ); +}; + +export type { PlainTextProps }; diff --git a/app/react/V2/Components/PDFViewer/index.ts b/app/react/V2/Components/PDFViewer/index.ts index ced8ba4ff11..576beaff719 100644 --- a/app/react/V2/Components/PDFViewer/index.ts +++ b/app/react/V2/Components/PDFViewer/index.ts @@ -5,3 +5,4 @@ const PDF = loadable(async () => import(/* webpackChunkName: "LazyLoadPDF" */ '. export { PDF, selectionHandlers }; export { calculateScaling } from './functions/calculateScaling'; +export { PlainText } from './PlainText'; diff --git a/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx b/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx index 58f85e57632..1a8e5c748f7 100644 --- a/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx +++ b/app/react/V2/Components/PDFViewer/specs/PDF.spec.tsx @@ -13,6 +13,22 @@ import * as helpers from '../functions/helpers'; configMocks({ act }); const oberserverMock = mockIntersectionObserver(); +class ResizeObserverMock { + callback: ResizeObserverCallback; + + constructor(callback: ResizeObserverCallback) { + this.callback = callback; + } + + observe = jest.fn(); + + unobserve = jest.fn(); + + disconnect = jest.fn(); +} + +global.ResizeObserver = ResizeObserverMock as any; + const highlights: PDFProps['highlights'] = { 2: [ { @@ -64,6 +80,8 @@ jest.mock('../pdfjs.ts', () => ({ }), destroy: mockPageDestroy, renderingState: 0, + scale: args.scale, + update: jest.fn(), cancelRendering: jest.fn(), }; }), @@ -168,4 +186,38 @@ describe('PDF', () => { expect(unobserveMock).toHaveBeenCalledTimes(5); }); }); + + describe('resize observer', () => { + let resizeObserverInstance: ResizeObserverMock | null = null; + + beforeEach(() => { + const OriginalResizeObserver = global.ResizeObserver; + global.ResizeObserver = jest.fn().mockImplementation((callback: ResizeObserverCallback) => { + resizeObserverInstance = new (OriginalResizeObserver as any)(callback); + return resizeObserverInstance; + }) as any; + }); + + afterEach(() => { + resizeObserverInstance = null; + }); + + it('should set up ResizeObserver for the PDF container', async () => { + await act(() => { + renderComponet(); + }); + + expect(resizeObserverInstance?.observe).toHaveBeenCalledTimes(1); + }); + + it('should disconnect ResizeObserver on unmount', async () => { + await act(() => { + renderComponet(); + }); + + cleanup(); + + expect(resizeObserverInstance?.disconnect).toHaveBeenCalledTimes(1); + }); + }); }); diff --git a/app/react/V2/Components/PDFViewer/specs/PlainText.spec.tsx b/app/react/V2/Components/PDFViewer/specs/PlainText.spec.tsx new file mode 100644 index 00000000000..62509ba259e --- /dev/null +++ b/app/react/V2/Components/PDFViewer/specs/PlainText.spec.tsx @@ -0,0 +1,43 @@ +/** + * @jest-environment jsdom + */ + +import React from 'react'; +import { render, cleanup } from '@testing-library/react'; +import { PlainText } from '../PlainText'; + +afterEach(() => cleanup()); + +describe('PlainText', () => { + it('renders the first page when page prop is not provided', () => { + const file = { fullText: { 2: 'Second page', 10: 'Tenth page' } }; + const { container } = render(); + + const el = container.querySelector('.whitespace-pre-line'); + expect(el?.textContent).toBe('Second page'); + }); + + it('renders the requested page when page prop is a number', () => { + const file = { fullText: { 1: 'One', 3: 'Three' } }; + const { container } = render(<PlainText file={file} page={3} />); + + const el = container.querySelector('.whitespace-pre-line'); + expect(el?.textContent).toBe('Three'); + }); + + it('renders the requested page when page prop is a string', () => { + const file = { fullText: { 1: 'One', 3: 'Three' } }; + const { container } = render(<PlainText file={file} page="1" />); + + const el = container.querySelector('.whitespace-pre-line'); + expect(el?.textContent).toBe('One'); + }); + + it('renders an empty string when the requested page does not exist', () => { + const file = { fullText: { 5: 'Five' } }; + const { container } = render(<PlainText file={file} page={2} />); + + const el = container.querySelector('.whitespace-pre-line'); + expect(el?.textContent).toBe(''); + }); +}); diff --git a/app/react/V2/Components/UI/Tabs.tsx b/app/react/V2/Components/UI/Tabs.tsx index 46d9601c04e..3c85e6a3be9 100644 --- a/app/react/V2/Components/UI/Tabs.tsx +++ b/app/react/V2/Components/UI/Tabs.tsx @@ -45,11 +45,11 @@ const Tabs = ({ } }; - const activeClass = 'text-gray-900 bg-gray-50'; + const activeClass = 'text-primary-900 bg-primary-50'; const inactiveClass = 'text-gray-500'; return ( - <div className={`flex flex-col h-full ${className ?? ''}`}> + <div className={`flex flex-col h-full gap-4 ${className ?? ''}`}> <div role="tablist" aria-orientation="horizontal" @@ -74,7 +74,7 @@ const Tabs = ({ </button> ))} </div> - <div className="py-4 grow overflow-y-auto"> + <div className="grow overflow-y-auto"> {tabChildren.map(child => { const isActive = child.props.id === activeTab; diff --git a/app/react/V2/Routes/Entity/Components/PDFView.tsx b/app/react/V2/Routes/Entity/Components/PDFView.tsx new file mode 100644 index 00000000000..a3a04ddc795 --- /dev/null +++ b/app/react/V2/Routes/Entity/Components/PDFView.tsx @@ -0,0 +1,76 @@ +import React, { useCallback } from 'react'; +import { useSearchParams } from 'react-router'; +import { t, Translate } from 'app/I18N'; +import { PDF, PlainText } from 'V2/Components/PDFViewer'; +import { Entity } from 'V2/domain'; +import { TemplateLabel } from 'V2/Components/Metadata'; +import { Truncate } from 'V2/Components/UI'; + +const PDFView = ({ entity }: { entity: Entity }) => { + const [searchParams, setSearchParams] = useSearchParams(); + + const onSelect = useCallback( + (event: React.ChangeEvent<HTMLSelectElement>) => { + const { value } = event.target; + const next = new URLSearchParams(searchParams.toString()); + if (value === 'raw') { + next.set('raw', 'true'); + } else { + next.delete('raw'); + } + setSearchParams(next, { replace: true, preventScrollReset: true }); + }, + [searchParams, setSearchParams] + ); + + if (!entity?.mainDocument) { + return <Translate>Loading</Translate>; + } + + const { filename, originalname } = entity.mainDocument; + + const isRaw = searchParams.get('raw') === 'true'; + const pageParam = searchParams.get('page') || undefined; + + return ( + <div className="flex flex-col h-full gap-2 min-h-0"> + <div className="w-full p-4 rounded-md bg-gray-50"> + <div className="flex flex-row justify-between gap-2"> + <div> + <TemplateLabel + label={entity.template?.label || ''} + templateId={entity.template?._id} + color={entity.template?.color} + /> + </div> + <div> + <label htmlFor="render-mode" className="sr-only"> + <Translate>View</Translate> + </label> + <select + id="render-mode" + className="bg-white rounded-md border-gr border-indigo-100 px-4 py-0 text-indigo-800" + value={isRaw ? 'raw' : 'normal'} + onChange={onSelect} + > + <option value="raw">{t('System', 'Plain text', null, false)}</option> + <option value="normal">{t('System', 'PDF', null, false)}</option> + </select> + </div> + </div> + <Truncate maxLength={80}> + <h2 className="font-bold text-gray-900 mt-2 text-lg">{originalname}</h2> + </Truncate> + </div> + <div className={`flex-1 min-h-0 overflow-y-auto ${isRaw ? 'hidden' : 'block'}`}> + <PDF fileUrl={`/api/files/${filename}`} scrollToPage={pageParam} /> + </div> + <div className={`flex-1 min-h-0 overflow-y-auto ${isRaw ? 'block' : 'hidden'}`}> + <PlainText file={entity.mainDocument} page={pageParam} /> + </div> + <div>footer</div> + </div> + ); +}; + +export { PDFView }; diff --git a/app/react/V2/Routes/Entity/Components/index.ts b/app/react/V2/Routes/Entity/Components/index.ts new file mode 100644 index 00000000000..a1e834ca4fd --- /dev/null +++ b/app/react/V2/Routes/Entity/Components/index.ts @@ -0,0 +1,2 @@ +export { PDFView } from './PDFView'; +export { TabLabel } from './TabLabel'; diff --git a/app/react/V2/Routes/Entity/Entity.tsx b/app/react/V2/Routes/Entity/Entity.tsx index 8ca53a1a2ab..4c926a0ea78 100644 --- a/app/react/V2/Routes/Entity/Entity.tsx +++ b/app/react/V2/Routes/Entity/Entity.tsx @@ -14,10 +14,9 @@ import { getEntityCompositionUseCase } from 'V2/application/container/singletons import { fullDetailOptions } from 'V2/application/optionsPresets'; import { PaneLayout } from 'V2/Components/Layouts/PaneLayout'; import { MetadataDisplay } from 'V2/Components/Metadata'; -import { PDF } from 'V2/Components/PDFViewer'; import { RelationshipPropertyIcon } from 'V2/Components/CustomIcons'; import { Tabs } from 'V2/Components/UI'; -import { TabLabel } from './Components/TabLabel'; +import { TabLabel, PDFView } from './Components'; const MAIN_TAB_PARAM = 'm'; const SIDE_TAB_PARAM = 's'; @@ -192,9 +191,9 @@ const Entity = () => { return ( <div className="tw-content"> <PaneLayout defaultWidthsPercents={[0.65, 0.35]} className="bg-white"> - <PaneLayout.Pane className="py-4 px-2 h-full"> + <PaneLayout.Pane className="p-2 h-full"> <Tabs - className="min-w-fit overflow-x-auto" + className="" unmountTabs={false} initialTabId={activeMainTab} onTabSelected={onMainTabChange} @@ -203,11 +202,7 @@ const Entity = () => { id={MAIN_TABS.DOCUMENT} label={<TabLabel text="Document" icon={<DocumentTextIcon className="w-5 h-5" />} />} > - {entity?.mainDocument?.filename ? ( - <PDF fileUrl={`/api/files/${entity.mainDocument.filename}`} /> - ) : ( - <Translate>Loading</Translate> - )} + <PDFView entity={entity} /> </Tabs.Tab> <Tabs.Tab id={MAIN_TABS.METADATA} @@ -230,7 +225,7 @@ const Entity = () => { </Tabs.Tab> </Tabs> </PaneLayout.Pane> - <PaneLayout.Pane className="py-4 px-2 h-full"> + <PaneLayout.Pane className="p-2 h-full"> <Tabs className="min-w-[300px] overflow-x-auto" key={activeMainTab} diff --git a/app/react/V2/Routes/Entity/specs/Entity.spec.tsx b/app/react/V2/Routes/Entity/specs/Entity.spec.tsx index 7a3f2f82baf..8b398a7eff8 100644 --- a/app/react/V2/Routes/Entity/specs/Entity.spec.tsx +++ b/app/react/V2/Routes/Entity/specs/Entity.spec.tsx @@ -8,6 +8,7 @@ import { TestRouterContext, setupMatchMediaMock } from 'V2/testing'; import { Entity, shouldRevalidate } from '../Entity'; jest.mock('V2/Components/PDFViewer', () => ({ + ...jest.requireActual('V2/Components/PDFViewer'), PDF: ({ fileUrl }: any) => <div data-testid="mock-pdf">PDF: {fileUrl}</div>, })); @@ -28,6 +29,11 @@ afterEach(() => { mediaMock = setupMatchMediaMock(); }); +const checkEntityRendered = async () => { + const titleElements = await screen.findAllByText('Sample Entity'); + expect(titleElements.length).toBeGreaterThan(0); +}; + describe('Entity view', () => { it('should show loading when no entity', async () => { render( @@ -46,8 +52,7 @@ describe('Entity view', () => { </TestRouterContext> ); - const titleElements = await screen.findAllByText('Sample Entity'); - expect(titleElements.length).toBeGreaterThan(0); + await checkEntityRendered(); expect(screen.getByTestId('mock-pdf')).toBeInTheDocument(); expect(screen.getByTestId('mock-pdf')).toHaveTextContent('/api/files/file.pdf'); @@ -61,8 +66,7 @@ describe('Entity view', () => { </TestRouterContext> ); - const titleElems = await screen.findAllByText('Sample Entity'); - expect(titleElems.length).toBeGreaterThan(0); + await checkEntityRendered(); }); it('should render the expected main tabs', () => { @@ -111,39 +115,95 @@ describe('Entity view', () => { }); }); }); -}); -describe('shouldRevalidate', () => { - it('should not revalidate when switching search params', () => { - const currentParams: any = { sharedId: 's1' }; - const nextParams: any = { sharedId: 's1' }; - const currentUrl: any = { pathname: '/entity/s1', search: '?main=metadata' }; - const nextUrl: any = { pathname: '/entity/s1', search: '?main=document' }; - const result = shouldRevalidate({ currentParams, nextParams, currentUrl, nextUrl } as any); - expect(result).toBe(false); - }); + describe('Plain text view', () => { + it('should switch to plain text view', async () => { + const mainDocumentFile = { + fullText: { 1: 'This is the plain text' }, + filename: 'file.pdf', + }; + + render( + <TestRouterContext loaderData={{ ...sampleEntity, mainDocument: mainDocumentFile }}> + <Entity /> + </TestRouterContext> + ); - it('should revalidate when sharedId changes', () => { - const currentParams: any = { sharedId: 's1' }; - const nextParams: any = { sharedId: 's2' }; - const currentUrl: any = { pathname: '/entity/s1', search: '?main=metadata' }; - const nextUrl: any = { pathname: '/entity/s2', search: '?main=metadata' }; - const result = shouldRevalidate({ currentParams, nextParams, currentUrl, nextUrl } as any); - expect(result).toBe(true); + await checkEntityRendered(); + + expect(screen.getByTestId('mock-pdf')).toBeInTheDocument(); + + expect(screen.getByText('This is the plain text').parentElement?.classList).toContain( + 'hidden' + ); + + const select = screen.getByRole('combobox'); + fireEvent.change(select, { target: { value: 'raw' } }); + + await waitFor(() => { + expect(screen.getByText('This is the plain text').parentElement?.classList).toContain( + 'block' + ); + }); + }); + + it('shoul display the plain text view page based on the url', async () => { + const mainDocumentFile = { + fullText: { 1: 'Shown from url plain text', 5: 'Page 5 content' }, + filename: 'file.pdf', + }; + + render( + <TestRouterContext + path="/entity/:sharedId" + loaderData={{ ...sampleEntity, mainDocument: mainDocumentFile }} + initialEntries={['/entity/shared1?raw=true&page=5']} + > + <Entity /> + </TestRouterContext> + ); + + await checkEntityRendered(); + + await waitFor(() => { + expect(screen.queryByText('Shown from url plain text')).not.toBeInTheDocument(); + expect(screen.getByText('Page 5 content').parentElement?.classList).toContain('block'); + }); + }); }); - it('should revalidate when params and sharedId are the same and defaultShouldRevalidate is true', () => { - const currentParams: any = { sharedId: 's1' }; - const nextParams: any = { sharedId: 's1' }; - const currentUrl: any = { pathname: '/entity/s1', search: '?m=1' }; - const nextUrl: any = { pathname: '/entity/s1', search: '?m=1' }; - const result = shouldRevalidate({ - currentParams, - nextParams, - currentUrl, - nextUrl, - defaultShouldRevalidate: true, - } as any); - expect(result).toBe(true); + describe('shouldRevalidate', () => { + it('should not revalidate when switching search params', () => { + const currentParams: any = { sharedId: 's1' }; + const nextParams: any = { sharedId: 's1' }; + const currentUrl: any = { pathname: '/entity/s1', search: '?main=metadata' }; + const nextUrl: any = { pathname: '/entity/s1', search: '?main=document' }; + const result = shouldRevalidate({ currentParams, nextParams, currentUrl, nextUrl } as any); + expect(result).toBe(false); + }); + + it('should revalidate when sharedId changes', () => { + const currentParams: any = { sharedId: 's1' }; + const nextParams: any = { sharedId: 's2' }; + const currentUrl: any = { pathname: '/entity/s1', search: '?main=metadata' }; + const nextUrl: any = { pathname: '/entity/s2', search: '?main=metadata' }; + const result = shouldRevalidate({ currentParams, nextParams, currentUrl, nextUrl } as any); + expect(result).toBe(true); + }); + + it('should revalidate when params and sharedId are the same and defaultShouldRevalidate is true', () => { + const currentParams: any = { sharedId: 's1' }; + const nextParams: any = { sharedId: 's1' }; + const currentUrl: any = { pathname: '/entity/s1', search: '?m=1' }; + const nextUrl: any = { pathname: '/entity/s1', search: '?m=1' }; + const result = shouldRevalidate({ + currentParams, + nextParams, + currentUrl, + nextUrl, + defaultShouldRevalidate: true, + } as any); + expect(result).toBe(true); + }); }); }); diff --git a/app/react/V2/api/entities/index.ts b/app/react/V2/api/entities/index.ts index 3930ae3875b..f5af97f1770 100644 --- a/app/react/V2/api/entities/index.ts +++ b/app/react/V2/api/entities/index.ts @@ -5,22 +5,19 @@ import { FetchResponseError } from 'shared/JSONRequest'; import { RequestParams } from 'app/utils/RequestParams'; import * as formatter from './formatter'; -type EntityApiParams = { - omitRelationships?: boolean; -}; +type EntityApiParams = { omitRelationships?: boolean; documentsFullText?: boolean }; const getById = async ({ _id, language, omitRelationships = true, -}: EntityApiParams & { - _id: string; - language: string; -}): Promise<EntitySchema[]> => { + documentsFullText = false, +}: EntityApiParams & { _id: string; language: string }): Promise<EntitySchema[]> => { try { const requestParams = new RequestParams({ _id, omitRelationships, + documentsFullText, }); api.locale(language); @@ -39,10 +36,8 @@ const getBySharedId = async ( sharedId, language, omitRelationships = true, - }: EntityApiParams & { - sharedId: string; - language: string; - }, + documentsFullText = false, + }: EntityApiParams & { sharedId: string; language: string }, headers?: IncomingHttpHeaders ): Promise<EntitySchema[]> => { try { @@ -50,6 +45,7 @@ const getBySharedId = async ( { sharedId, omitRelationships, + documentsFullText, }, headers ); diff --git a/app/react/V2/application/useCases/EntityCompositionUseCase.ts b/app/react/V2/application/useCases/EntityCompositionUseCase.ts index e478928bd6a..704ae934918 100644 --- a/app/react/V2/application/useCases/EntityCompositionUseCase.ts +++ b/app/react/V2/application/useCases/EntityCompositionUseCase.ts @@ -65,7 +65,11 @@ export class EntityCompositionUseCase { ): Promise<CompositionResult> { try { const response = await this.repository.getBySharedId( - { sharedId: entityId, language: this.atomStore.get(localeAtom) || 'en' }, + { + sharedId: entityId, + language: this.atomStore.get(localeAtom) || 'en', + documentsFullText: options.includeSupportingFiles, + }, context.headers || {} ); if (!response || response.length === 0) { diff --git a/app/react/V2/infrastructure/repositories/EntityRepository.ts b/app/react/V2/infrastructure/repositories/EntityRepository.ts index 489612fa552..c0eb7a798ea 100644 --- a/app/react/V2/infrastructure/repositories/EntityRepository.ts +++ b/app/react/V2/infrastructure/repositories/EntityRepository.ts @@ -7,6 +7,7 @@ export interface EntityRepository { sharedId: string; language: string; omitRelationships?: boolean; + documentsFullText?: boolean; }, headers?: IncomingHttpHeaders ): Promise<EntitySchema[]>; diff --git a/app/react/V2/infrastructure/repositories/EntityRepositoryImpl.ts b/app/react/V2/infrastructure/repositories/EntityRepositoryImpl.ts index 2573a934c53..df7f406bb18 100644 --- a/app/react/V2/infrastructure/repositories/EntityRepositoryImpl.ts +++ b/app/react/V2/infrastructure/repositories/EntityRepositoryImpl.ts @@ -6,15 +6,23 @@ import * as searchEntitiesApi from '../../api/search/index'; export class EntityRepositoryImpl implements EntityRepository { constructor() {} + async getBySharedId( - options: { sharedId: string; language: string; omitRelationships?: boolean }, + options: { + sharedId: string; + language: string; + omitRelationships?: boolean; + documentsFullText?: boolean; + }, headers?: IncomingHttpHeaders ): Promise<EntitySchema[]> { return entitiesApi.getBySharedId(options, headers); } + async save(_entity: EntitySchema): Promise<EntitySchema> { throw new Error('Method not implemented.'); } + async getBySharedIds( options: { sharedIds: string[]; language: string; omitRelationships?: boolean }, headers?: IncomingHttpHeaders