diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.js b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.js
new file mode 100644
index 0000000000..f4bfdb2207
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-content.js
@@ -0,0 +1,37 @@
+import { useTranslation } from 'react-i18next'
+import PdfLogEntryRawContent from './pdf-log-entry-raw-content'
+import PropTypes from 'prop-types'
+
+export default function PdfLogEntryContent({
+ rawContent,
+ formattedContent,
+ extraInfoURL,
+}) {
+ const { t } = useTranslation()
+
+ return (
+
+ {formattedContent && (
+
{formattedContent}
+ )}
+
+ {extraInfoURL && (
+
+ )}
+
+ {rawContent && (
+
+ )}
+
+ )
+}
+
+PdfLogEntryContent.propTypes = {
+ rawContent: PropTypes.string,
+ formattedContent: PropTypes.node,
+ extraInfoURL: PropTypes.string,
+}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.js b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.js
new file mode 100644
index 0000000000..326dd614cf
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry-raw-content.js
@@ -0,0 +1,66 @@
+import { useState } from 'react'
+import { useResizeObserver } from '../hooks/use-resize-observer'
+import { useTranslation } from 'react-i18next'
+import classNames from 'classnames'
+import { Button } from 'react-bootstrap'
+import Icon from '../../../shared/components/icon'
+import PropTypes from 'prop-types'
+
+export default function PdfLogEntryRawContent({
+ rawContent,
+ collapsedSize = 0,
+}) {
+ const [expanded, setExpanded] = useState(false)
+ const [needsExpander, setNeedsExpander] = useState(false)
+
+ const containerRef = useResizeObserver(entry => {
+ setNeedsExpander(entry.target.scrollHeight > collapsedSize)
+ })
+
+ const { t } = useTranslation()
+
+ return (
+
+
+
+ {rawContent.trim()}
+
+
+
+ {needsExpander && (
+
+
+
+ )}
+
+ )
+}
+
+PdfLogEntryRawContent.propTypes = {
+ rawContent: PropTypes.string.isRequired,
+ collapsedSize: PropTypes.number,
+}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.js b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.js
new file mode 100644
index 0000000000..b70c891a5d
--- /dev/null
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-log-entry.js
@@ -0,0 +1,76 @@
+import PropTypes from 'prop-types'
+import classNames from 'classnames'
+import { memo, useCallback } from 'react'
+import PreviewLogEntryHeader from '../../preview/components/preview-log-entry-header'
+import PdfLogEntryContent from './pdf-log-entry-content'
+
+function PdfLogEntry({
+ headerTitle,
+ headerIcon,
+ rawContent,
+ logType,
+ formattedContent,
+ extraInfoURL,
+ level,
+ sourceLocation,
+ showSourceLocationLink = true,
+ showCloseButton = false,
+ entryAriaLabel = null,
+ customClass,
+ onSourceLocationClick,
+ onClose,
+}) {
+ const handleLogEntryLinkClick = useCallback(
+ event => {
+ event.preventDefault()
+ onSourceLocationClick(sourceLocation)
+ },
+ [onSourceLocationClick, sourceLocation]
+ )
+
+ return (
+
+
+ {(rawContent || formattedContent) && (
+
+ )}
+
+ )
+}
+
+PdfLogEntry.propTypes = {
+ sourceLocation: PreviewLogEntryHeader.propTypes.sourceLocation,
+ headerTitle: PropTypes.string,
+ headerIcon: PropTypes.element,
+ rawContent: PropTypes.string,
+ logType: PropTypes.string,
+ formattedContent: PropTypes.node,
+ extraInfoURL: PropTypes.string,
+ level: PropTypes.oneOf(['error', 'warning', 'typesetting', 'raw', 'success'])
+ .isRequired,
+ customClass: PropTypes.string,
+ showSourceLocationLink: PropTypes.bool,
+ showCloseButton: PropTypes.bool,
+ entryAriaLabel: PropTypes.string,
+ onSourceLocationClick: PropTypes.func,
+ onClose: PropTypes.func,
+}
+
+export default memo(PdfLogEntry)
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js
index 1587906de2..552e431f21 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-logs-entries.js
@@ -1,8 +1,8 @@
import { memo, useCallback } from 'react'
import PropTypes from 'prop-types'
import { useTranslation } from 'react-i18next'
-import PreviewLogsPaneEntry from '../../preview/components/preview-logs-pane-entry'
import PreviewLogsPaneMaxEntries from '../../preview/components/preview-logs-pane-max-entries'
+import PdfLogEntry from './pdf-log-entry'
const LOG_PREVIEW_LIMIT = 100
@@ -23,13 +23,12 @@ function PdfLogsEntries({ entries }) {
<>
{entries.length > LOG_PREVIEW_LIMIT && (
)}
{logEntries.map(logEntry => (
- ', function () {
// xhrMock.teardown()
fetchMock.reset()
localStorage.clear()
+ sinon.restore()
})
it('renders the PDF preview', async function () {
@@ -270,6 +271,35 @@ describe('', function () {
expect(fetchMock.called('express:/build/:file')).to.be.false // TODO: actual path
})
+ it('displays expandable raw logs', async function () {
+ mockCompile()
+ mockBuildFile()
+
+ // pretend that the content is large enough to trigger a "collapse"
+ // (in jsdom these values are always zero)
+ sinon.stub(HTMLElement.prototype, 'scrollHeight').value(500)
+ sinon.stub(HTMLElement.prototype, 'scrollWidth').value(500)
+
+ renderWithEditorContext(, { scope })
+
+ // wait for "compile on load" to finish
+ await screen.findByRole('button', { name: 'Compiling…' })
+ await screen.findByRole('button', { name: 'Recompile' })
+
+ const logsButton = screen.getByRole('button', { name: 'View logs' })
+ logsButton.click()
+
+ await screen.findByRole('button', { name: 'View PDF' })
+
+ // expand the log
+ const expandButton = screen.getByRole('button', { name: 'Expand' })
+ expandButton.click()
+
+ // collapse the log
+ const collapseButton = screen.getByRole('button', { name: 'Collapse' })
+ collapseButton.click()
+ })
+
it('displays error messages if there were validation problems', async function () {
const validationProblems = {
sizeCheck: {