mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-25 04:24:43 -04: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 }
|
headers: { 'Last-Modified': MOCK_LAST_MODIFIED }
|
||||||
})
|
})
|
||||||
cy.visitHome()
|
cy.visitHome()
|
||||||
cy.getByCypressId('motd-modal').find('.markdown-body').should('contain.html', motdMockHtml)
|
cy.getMotdBody().should('contain.html', motdMockHtml)
|
||||||
cy.getByCypressId('motd-dismiss')
|
cy.getByCypressId('motd-dismiss')
|
||||||
.click()
|
.click()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
|
|
@ -7,12 +7,16 @@
|
||||||
import { RendererType } from '../../src/components/render-page/window-post-message-communicator/rendering-message'
|
import { RendererType } from '../../src/components/render-page/window-post-message-communicator/rendering-message'
|
||||||
|
|
||||||
declare namespace Cypress {
|
declare namespace Cypress {
|
||||||
interface Chainable {
|
interface Chainable<Subject = unknown> {
|
||||||
getIframeBody(rendererType?: RendererType): Chainable<Element>
|
getIframeBody(rendererType?: RendererType): Chainable<Element>
|
||||||
|
|
||||||
getReveal(): Chainable<Element>
|
getReveal(): Chainable<Element>
|
||||||
|
|
||||||
getMarkdownBody(): Chainable<Element>
|
getMarkdownBody(): Chainable<Element>
|
||||||
|
|
||||||
|
getIntroBody(): Chainable<Element>
|
||||||
|
|
||||||
|
getMotdBody(): Chainable<Element>
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,3 +43,7 @@ Cypress.Commands.add('getMarkdownBody', () => {
|
||||||
Cypress.Commands.add('getIntroBody', () => {
|
Cypress.Commands.add('getIntroBody', () => {
|
||||||
return cy.getIframeBody(RendererType.INTRO).findByCypressId('markdown-body')
|
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
|
// 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`] = `
|
exports[`motd modal doesn't render a modal if no motd has been fetched 1`] = `
|
||||||
<div>
|
<div>
|
||||||
<span
|
<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:
|
This is a mock implementation of a Modal:
|
||||||
<dialog>
|
<dialog>
|
||||||
<div
|
<div
|
||||||
class="modal-body"
|
class="bg-light modal-body"
|
||||||
>
|
>
|
||||||
<div
|
<span
|
||||||
class="markdown-body"
|
|
||||||
data-testid="motd-renderer"
|
data-testid="motd-renderer"
|
||||||
>
|
>
|
||||||
<p>
|
This is a mock implementation of a iframe renderer. Props:
|
||||||
very important mock text!
|
{"frameClasses":"w-100","rendererType":"motd","markdownContentLines":["very important mock text!"],"adaptFrameHeightToContent":true}
|
||||||
</p>
|
</span>
|
||||||
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
class="modal-footer"
|
class="modal-footer"
|
||||||
|
|
|
@ -12,9 +12,12 @@ import type { PropsWithChildren } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { CommonModalProps } from '../modals/common-modal'
|
import type { CommonModalProps } from '../modals/common-modal'
|
||||||
import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n'
|
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('./fetch-motd')
|
||||||
jest.mock('../modals/common-modal')
|
jest.mock('../modals/common-modal')
|
||||||
|
jest.mock('../../editor-page/renderer-pane/render-iframe')
|
||||||
|
|
||||||
describe('motd modal', () => {
|
describe('motd modal', () => {
|
||||||
beforeAll(mockI18n)
|
beforeAll(mockI18n)
|
||||||
|
@ -28,11 +31,17 @@ describe('motd modal', () => {
|
||||||
jest.spyOn(CommonModalModule, 'CommonModal').mockImplementation((({ children, show }) => {
|
jest.spyOn(CommonModalModule, 'CommonModal').mockImplementation((({ children, show }) => {
|
||||||
return (
|
return (
|
||||||
<span>
|
<span>
|
||||||
This is a mock implementation of a Modal:
|
This is a mock implementation of a Modal: {show ? <dialog>{children}</dialog> : 'Modal is invisible'}
|
||||||
{show ? <dialog>{children}</dialog> : 'Modal is invisible'}
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}) as React.FC<PropsWithChildren<CommonModalProps>>)
|
}) 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 () => {
|
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')
|
await screen.findByTestId('loaded not visible')
|
||||||
expect(view.container).toMatchSnapshot()
|
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
|
* 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 { Button, Modal } from 'react-bootstrap'
|
||||||
import { CommonModal } from '../modals/common-modal'
|
import { CommonModal } from '../modals/common-modal'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
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 { useAsync } from 'react-use'
|
||||||
import { Logger } from '../../../utils/logger'
|
import { Logger } from '../../../utils/logger'
|
||||||
import { testId } from '../../../utils/test-id'
|
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'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
|
|
||||||
const MotdRenderer = React.lazy(() => import('./motd-renderer'))
|
|
||||||
const logger = new Logger('Motd')
|
const logger = new Logger('Motd')
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +31,8 @@ export const MotdModal: React.FC = () => {
|
||||||
const { error, loading, value } = useAsync(fetchMotd)
|
const { error, loading, value } = useAsync(fetchMotd)
|
||||||
const [dismissed, setDismissed] = useState(false)
|
const [dismissed, setDismissed] = useState(false)
|
||||||
|
|
||||||
|
const lines = useMemo(() => value?.motdText.split('\n'), [value?.motdText])
|
||||||
|
|
||||||
const dismiss = useCallback(() => {
|
const dismiss = useCallback(() => {
|
||||||
if (value?.lastModified) {
|
if (value?.lastModified) {
|
||||||
window.localStorage.setItem(MOTD_LOCAL_STORAGE_KEY, value.lastModified)
|
window.localStorage.setItem(MOTD_LOCAL_STORAGE_KEY, value.lastModified)
|
||||||
|
@ -47,10 +51,17 @@ export const MotdModal: React.FC = () => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonModal show={!!value && !loading && !error && !dismissed} title={'motd.title'} {...cypressId('motd-modal')}>
|
<CommonModal show={!!lines && !loading && !error && !dismissed} title={'motd.title'} {...cypressId('motd-modal')}>
|
||||||
<Modal.Body>
|
<Modal.Body className={'bg-light'}>
|
||||||
<Suspense fallback={<WaitSpinner />}>
|
<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>
|
</Suspense>
|
||||||
</Modal.Body>
|
</Modal.Body>
|
||||||
<Modal.Footer>
|
<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}
|
slideOptions={slideOptions}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
case RendererType.MOTD:
|
||||||
case RendererType.INTRO:
|
case RendererType.INTRO:
|
||||||
return (
|
return (
|
||||||
<MarkdownDocument
|
<MarkdownDocument
|
||||||
|
|
|
@ -137,7 +137,8 @@ export type RendererToEditorMessageType =
|
||||||
export enum RendererType {
|
export enum RendererType {
|
||||||
DOCUMENT = 'document',
|
DOCUMENT = 'document',
|
||||||
INTRO = 'intro',
|
INTRO = 'intro',
|
||||||
SLIDESHOW = 'slideshow'
|
SLIDESHOW = 'slideshow',
|
||||||
|
MOTD = 'motd'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseConfiguration {
|
export interface BaseConfiguration {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue