mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
feat(motd): use iframe renderer for motd
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
579919f142
commit
ba96f07374
8 changed files with 47 additions and 120 deletions
|
@ -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(() => {
|
||||
|
|
|
@ -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')
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
<iframe></iframe>
|
||||
</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
|
||||
|
@ -46,21 +11,17 @@ exports[`motd modal doesn't render a modal if no motd has been fetched 1`] = `
|
|||
exports[`motd modal renders a modal if a motd was fetched and can dismiss it 1`] = `
|
||||
<div>
|
||||
<span>
|
||||
This is a mock implementation of a Modal:
|
||||
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"
|
||||
|
@ -81,7 +42,7 @@ exports[`motd modal renders a modal if a motd was fetched and can dismiss it 1`]
|
|||
exports[`motd modal renders a modal if a motd was fetched and can dismiss it 2`] = `
|
||||
<div>
|
||||
<span>
|
||||
This is a mock implementation of a Modal:
|
||||
This is a mock implementation of a Modal:
|
||||
Modal is invisible
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
})
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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
|
|
@ -159,6 +159,7 @@ export const IframeMarkdownRenderer: React.FC = () => {
|
|||
slideOptions={slideOptions}
|
||||
/>
|
||||
)
|
||||
case RendererType.MOTD:
|
||||
case RendererType.INTRO:
|
||||
return (
|
||||
<MarkdownDocument
|
||||
|
|
|
@ -137,7 +137,8 @@ export type RendererToEditorMessageType =
|
|||
export enum RendererType {
|
||||
DOCUMENT = 'document',
|
||||
INTRO = 'intro',
|
||||
SLIDESHOW = 'slideshow'
|
||||
SLIDESHOW = 'slideshow',
|
||||
MOTD = 'motd'
|
||||
}
|
||||
|
||||
export interface BaseConfiguration {
|
||||
|
|
Loading…
Reference in a new issue