feat(motd): use iframe renderer for motd

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-10-06 21:56:52 +02:00
parent 579919f142
commit ba96f07374
8 changed files with 47 additions and 120 deletions

View file

@ -23,7 +23,7 @@ describe('Motd', () => {
headers: { 'Last-Modified': MOCK_LAST_MODIFIED }
})
cy.visitHome()
cy.getByCypressId('motd-modal').find('.markdown-body').should('contain.html', motdMockHtml)
cy.getMotdBody().should('contain.html', motdMockHtml)
cy.getByCypressId('motd-dismiss')
.click()
.then(() => {

View file

@ -7,12 +7,16 @@
import { RendererType } from '../../src/components/render-page/window-post-message-communicator/rendering-message'
declare namespace Cypress {
interface Chainable {
interface Chainable<Subject = unknown> {
getIframeBody(rendererType?: RendererType): Chainable<Element>
getReveal(): Chainable<Element>
getMarkdownBody(): Chainable<Element>
getIntroBody(): Chainable<Element>
getMotdBody(): Chainable<Element>
}
}
@ -39,3 +43,7 @@ Cypress.Commands.add('getMarkdownBody', () => {
Cypress.Commands.add('getIntroBody', () => {
return cy.getIframeBody(RendererType.INTRO).findByCypressId('markdown-body')
})
Cypress.Commands.add('getMotdBody', () => {
return cy.getIframeBody(RendererType.MOTD).findByCypressId('markdown-body')
})

View file

@ -1,40 +1,5 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`motd modal doesn't allow html in the motd 1`] = `
<div>
<span>
This is a mock implementation of a Modal:
<dialog>
<div
class="modal-body"
>
<div
class="markdown-body"
data-testid="motd-renderer"
>
<p>
&lt;iframe&gt;&lt;/iframe&gt;
</p>
</div>
</div>
<div
class="modal-footer"
>
<button
class="btn btn-success"
data-testid="motd-dismiss"
type="button"
>
common.dismiss
</button>
</div>
</dialog>
</span>
</div>
`;
exports[`motd modal doesn't render a modal if no motd has been fetched 1`] = `
<div>
<span
@ -49,18 +14,14 @@ exports[`motd modal renders a modal if a motd was fetched and can dismiss it 1`]
This is a mock implementation of a Modal:
<dialog>
<div
class="modal-body"
class="bg-light modal-body"
>
<div
class="markdown-body"
<span
data-testid="motd-renderer"
>
<p>
very important mock text!
</p>
</div>
This is a mock implementation of a iframe renderer. Props:
{"frameClasses":"w-100","rendererType":"motd","markdownContentLines":["very important mock text!"],"adaptFrameHeightToContent":true}
</span>
</div>
<div
class="modal-footer"

View file

@ -12,9 +12,12 @@ import type { PropsWithChildren } from 'react'
import React from 'react'
import type { CommonModalProps } from '../modals/common-modal'
import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n'
import * as RenderIframeModule from '../../editor-page/renderer-pane/render-iframe'
import { testId } from '../../../utils/test-id'
jest.mock('./fetch-motd')
jest.mock('../modals/common-modal')
jest.mock('../../editor-page/renderer-pane/render-iframe')
describe('motd modal', () => {
beforeAll(mockI18n)
@ -28,11 +31,17 @@ describe('motd modal', () => {
jest.spyOn(CommonModalModule, 'CommonModal').mockImplementation((({ children, show }) => {
return (
<span>
This is a mock implementation of a Modal:
{show ? <dialog>{children}</dialog> : 'Modal is invisible'}
This is a mock implementation of a Modal: {show ? <dialog>{children}</dialog> : 'Modal is invisible'}
</span>
)
}) as React.FC<PropsWithChildren<CommonModalProps>>)
jest.spyOn(RenderIframeModule, 'RenderIframe').mockImplementation((props) => {
return (
<span {...testId('motd-renderer')}>
This is a mock implementation of a iframe renderer. Props: {JSON.stringify(props)}
</span>
)
})
})
it('renders a modal if a motd was fetched and can dismiss it', async () => {
@ -61,13 +70,4 @@ describe('motd modal', () => {
await screen.findByTestId('loaded not visible')
expect(view.container).toMatchSnapshot()
})
it("doesn't allow html in the motd", async () => {
jest.spyOn(fetchMotdModule, 'fetchMotd').mockImplementation(() => {
return Promise.resolve({ motdText: '<iframe></iframe>', lastModified: 'yesterday' })
})
const view = render(<MotdModal></MotdModal>)
await screen.findByTestId('motd-renderer')
expect(view.container).toMatchSnapshot()
})
})

View file

@ -4,7 +4,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { Suspense, useCallback, useEffect, useState } from 'react'
import React, { Suspense, useCallback, useMemo, useEffect, useState } from 'react'
import { Button, Modal } from 'react-bootstrap'
import { CommonModal } from '../modals/common-modal'
import { Trans, useTranslation } from 'react-i18next'
@ -13,9 +13,11 @@ import { fetchMotd, MOTD_LOCAL_STORAGE_KEY } from './fetch-motd'
import { useAsync } from 'react-use'
import { Logger } from '../../../utils/logger'
import { testId } from '../../../utils/test-id'
import { RenderIframe } from '../../editor-page/renderer-pane/render-iframe'
import { RendererType } from '../../render-page/window-post-message-communicator/rendering-message'
import { EditorToRendererCommunicatorContextProvider } from '../../editor-page/render-context/editor-to-renderer-communicator-context-provider'
import { cypressId } from '../../../utils/cypress-attribute'
const MotdRenderer = React.lazy(() => import('./motd-renderer'))
const logger = new Logger('Motd')
/**
@ -29,6 +31,8 @@ export const MotdModal: React.FC = () => {
const { error, loading, value } = useAsync(fetchMotd)
const [dismissed, setDismissed] = useState(false)
const lines = useMemo(() => value?.motdText.split('\n'), [value?.motdText])
const dismiss = useCallback(() => {
if (value?.lastModified) {
window.localStorage.setItem(MOTD_LOCAL_STORAGE_KEY, value.lastModified)
@ -47,10 +51,17 @@ export const MotdModal: React.FC = () => {
}
return (
<CommonModal show={!!value && !loading && !error && !dismissed} title={'motd.title'} {...cypressId('motd-modal')}>
<Modal.Body>
<CommonModal show={!!lines && !loading && !error && !dismissed} title={'motd.title'} {...cypressId('motd-modal')}>
<Modal.Body className={'bg-light'}>
<Suspense fallback={<WaitSpinner />}>
<MotdRenderer content={value?.motdText as string} />
<EditorToRendererCommunicatorContextProvider>
<RenderIframe
frameClasses={'w-100'}
rendererType={RendererType.MOTD}
markdownContentLines={lines as string[]}
adaptFrameHeightToContent={true}
/>
</EditorToRendererCommunicatorContextProvider>
</Suspense>
</Modal.Body>
<Modal.Footer>

View file

@ -1,55 +0,0 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import React, { useMemo } from 'react'
import { GenericSyntaxMarkdownExtension } from '../../markdown-renderer/markdown-extension/generic-syntax-markdown-extension'
import { useConvertMarkdownToReactDom } from '../../markdown-renderer/hooks/use-convert-markdown-to-react-dom'
import { LinkifyFixMarkdownExtension } from '../../markdown-renderer/markdown-extension/linkify-fix-markdown-extension'
import { EmojiMarkdownExtension } from '../../markdown-renderer/markdown-extension/emoji/emoji-markdown-extension'
import { DebuggerMarkdownExtension } from '../../markdown-renderer/markdown-extension/debugger-markdown-extension'
import { ProxyImageMarkdownExtension } from '../../markdown-renderer/markdown-extension/image/proxy-image-markdown-extension'
import { YoutubeMarkdownExtension } from '../../markdown-renderer/markdown-extension/youtube/youtube-markdown-extension'
import { AlertMarkdownExtension } from '../../markdown-renderer/markdown-extension/alert-markdown-extension'
import { SpoilerMarkdownExtension } from '../../markdown-renderer/markdown-extension/spoiler-markdown-extension'
import { BlockquoteExtraTagMarkdownExtension } from '../../markdown-renderer/markdown-extension/blockquote/blockquote-extra-tag-markdown-extension'
import { VimeoMarkdownExtension } from '../../markdown-renderer/markdown-extension/vimeo/vimeo-markdown-extension'
import { testId } from '../../../utils/test-id'
export interface MotdRendererProps {
content: string
}
/**
* Reads the motd from the global application state and renders it as markdown with a subset of the usual features and without HTML support.
*/
export const MotdRenderer: React.FC<MotdRendererProps> = ({ content }) => {
const extensions = useMemo(
() => [
new YoutubeMarkdownExtension(),
new VimeoMarkdownExtension(),
new ProxyImageMarkdownExtension(),
new BlockquoteExtraTagMarkdownExtension(),
new AlertMarkdownExtension(),
new SpoilerMarkdownExtension(),
new GenericSyntaxMarkdownExtension(),
new LinkifyFixMarkdownExtension(),
new EmojiMarkdownExtension(),
new DebuggerMarkdownExtension()
],
[]
)
const lines = useMemo(() => content.split('\n'), [content])
const dom = useConvertMarkdownToReactDom(lines, extensions, true, false)
return (
<div {...testId('motd-renderer')} className={'markdown-body'}>
{dom}
</div>
)
}
export default MotdRenderer

View file

@ -159,6 +159,7 @@ export const IframeMarkdownRenderer: React.FC = () => {
slideOptions={slideOptions}
/>
)
case RendererType.MOTD:
case RendererType.INTRO:
return (
<MarkdownDocument

View file

@ -137,7 +137,8 @@ export type RendererToEditorMessageType =
export enum RendererType {
DOCUMENT = 'document',
INTRO = 'intro',
SLIDESHOW = 'slideshow'
SLIDESHOW = 'slideshow',
MOTD = 'motd'
}
export interface BaseConfiguration {