mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-04-14 22:33:30 +00:00
Add custom intro page by fetching markdown content from a file (#697)
Signed-off-by: Tilman Vatteroth <tilman.vatteroth@tu-dortmund.de>
This commit is contained in:
parent
4b2e2a7c93
commit
7f6e0e53a7
31 changed files with 373 additions and 173 deletions
|
@ -27,6 +27,10 @@ Files: public/index.html
|
|||
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
License: CC0-1.0
|
||||
|
||||
Files: public/intro.md
|
||||
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
License: CC0-1.0
|
||||
|
||||
Files: public/robots.txt
|
||||
Copyright: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
License: CC0-1.0
|
||||
|
|
|
@ -69,6 +69,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|||
- Surround selected text with a link via shortcut (ctrl+k or cmd+k).
|
||||
- A sidebar for menu options
|
||||
- Improved security by wrapping the markdown rendering into an iframe
|
||||
- The intro page content can be changed by editing `public/intro.md`
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
@ -5,50 +5,58 @@
|
|||
*/
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
||||
describe('Intro', () => {
|
||||
describe('Intro page', () => {
|
||||
beforeEach(() => {
|
||||
cy.intercept('/intro.md', 'test content')
|
||||
cy.visit('/')
|
||||
})
|
||||
|
||||
describe('Cover Button are hidden when logged in', () => {
|
||||
it('Sign in Cover Button', () => {
|
||||
cy.get('.cover-button.btn-success')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('Features Cover Button', () => {
|
||||
cy.get('.cover-button.btn-primary')
|
||||
.should('not.exist')
|
||||
describe('content', () => {
|
||||
it('fetches and shows the correct intro page content', () => {
|
||||
cy.getMarkdownBody()
|
||||
.contains('test content')
|
||||
})
|
||||
})
|
||||
|
||||
describe('Cover Button are shown when logged out', () => {
|
||||
beforeEach(() => {
|
||||
describe('features button', () => {
|
||||
it('is hidden when logged in', () => {
|
||||
cy.get('[data-cy="features-button"]')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('is visible when logged out', () => {
|
||||
cy.logout()
|
||||
})
|
||||
|
||||
it('Sign in Cover Button', () => {
|
||||
cy.get('.cover-button.btn-success')
|
||||
.should('exist')
|
||||
})
|
||||
|
||||
it('Features Cover Button', () => {
|
||||
cy.get('.cover-button.btn-primary')
|
||||
cy.get('[data-cy="features-button"]')
|
||||
.should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
it('Version can be opened and closed', () => {
|
||||
cy.get('#versionModal')
|
||||
.should('not.exist')
|
||||
cy.get('#version')
|
||||
.click()
|
||||
cy.get('#versionModal')
|
||||
.should('be.visible')
|
||||
cy.get('#versionModal .modal-footer .btn')
|
||||
.contains('Close')
|
||||
.click()
|
||||
cy.get('#versionModal')
|
||||
.should('not.exist')
|
||||
describe('sign in button', () => {
|
||||
it('is hidden when logged in', () => {
|
||||
cy.get('[data-cy="sign-in-button"]')
|
||||
.should('not.exist')
|
||||
})
|
||||
|
||||
it('is visible when logged out', () => {
|
||||
cy.logout()
|
||||
cy.get('[data-cy="sign-in-button"]')
|
||||
.should('exist')
|
||||
})
|
||||
})
|
||||
|
||||
describe('version dialog', () => {
|
||||
it('can be opened and closed', () => {
|
||||
cy.get('[data-cy="version-modal"]')
|
||||
.should('not.exist')
|
||||
cy.get('[data-cy="show-version-modal"]')
|
||||
.click()
|
||||
cy.get('[data-cy="version-modal"]')
|
||||
.should('be.visible')
|
||||
cy.get('[data-cy="version-modal"] [data-cy="close-version-modal-button"]')
|
||||
.contains('Close')
|
||||
.click()
|
||||
cy.get('[data-cy="version-modal"]')
|
||||
.should('not.exist')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -94,21 +94,21 @@ describe('Links Intro', () => {
|
|||
|
||||
describe('Feature Links', () => {
|
||||
it('Share-Notes', () => {
|
||||
cy.get('i.fa-bolt.fa-3x')
|
||||
cy.get('i.fa-bolt')
|
||||
.click()
|
||||
cy.url()
|
||||
.should('include', '/features#Share-Notes')
|
||||
})
|
||||
|
||||
it('KaTeX', () => {
|
||||
cy.get('i.fa-bar-chart.fa-3x')
|
||||
cy.get('i.fa-bar-chart')
|
||||
.click()
|
||||
cy.url()
|
||||
.should('include', '/features#MathJax')
|
||||
})
|
||||
|
||||
it('Slide-Mode', () => {
|
||||
cy.get('i.fa-television.fa-3x')
|
||||
cy.get('i.fa-television')
|
||||
.click()
|
||||
cy.url()
|
||||
.should('include', '/features#Slide-Mode')
|
||||
|
|
|
@ -14,6 +14,7 @@ declare namespace Cypress {
|
|||
|
||||
Cypress.Commands.add('getMarkdownRenderer', () => {
|
||||
return cy.get(`iframe[data-cy="documentIframe"]`)
|
||||
.should('be.visible')
|
||||
.its('0.contentDocument')
|
||||
.should('exist')
|
||||
.its('body')
|
||||
|
|
5
public/intro.md
Normal file
5
public/intro.md
Normal file
|
@ -0,0 +1,5 @@
|
|||
:::warning
|
||||
What you see is an UI-Test! It's filled with dummy data, not connected to a backend and no data will be saved.
|
||||
:::
|
||||
|
||||

|
|
@ -34,7 +34,8 @@
|
|||
"katex": "Works with charts and KaTeX",
|
||||
"slides": "Supports slide mode"
|
||||
},
|
||||
"screenShotAltText": "HedgeDoc Screenshot"
|
||||
"markdownWhileLoading": "Loading...",
|
||||
"markdownLoadingError": "Error while fetching intro content"
|
||||
},
|
||||
"history": {
|
||||
"error": {
|
||||
|
|
BIN
public/screenshot.png
Normal file
BIN
public/screenshot.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 250 KiB |
|
@ -22,6 +22,7 @@ import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
|||
import { DocumentInfobar } from './document-infobar'
|
||||
import { ErrorWhileLoadingNoteAlert } from './ErrorWhileLoadingNoteAlert'
|
||||
import { LoadingNoteAlert } from './LoadingNoteAlert'
|
||||
import { RendererType } from '../render-page/rendering-message'
|
||||
|
||||
export const DocumentReadOnlyPage: React.FC = () => {
|
||||
|
||||
|
@ -55,10 +56,11 @@ export const DocumentReadOnlyPage: React.FC = () => {
|
|||
noteId={ id }
|
||||
viewCount={ noteDetails.viewCount }
|
||||
/>
|
||||
<RenderIframe extraClasses={ 'flex-fill' }
|
||||
<RenderIframe frameClasses={ 'flex-fill h-100 w-100' }
|
||||
markdownContent={ markdownContent }
|
||||
onFirstHeadingChange={ onFirstHeadingChange }
|
||||
onFrontmatterChange={ onFrontmatterChange }/>
|
||||
onFrontmatterChange={ onFrontmatterChange }
|
||||
rendererType={RendererType.DOCUMENT}/>
|
||||
</ShowIf>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -32,6 +32,7 @@ import { RenderIframe } from './renderer-pane/render-iframe'
|
|||
import { Sidebar } from './sidebar/sidebar'
|
||||
import { Splitter } from './splitter/splitter'
|
||||
import { DualScrollState, ScrollState } from './synced-scroll/scroll-props'
|
||||
import { RendererType } from '../render-page/rendering-message'
|
||||
|
||||
export interface EditorPagePathParams {
|
||||
id: string
|
||||
|
@ -115,13 +116,15 @@ export const EditorPage: React.FC = () => {
|
|||
showRight={ editorMode === EditorMode.PREVIEW || editorMode === EditorMode.BOTH }
|
||||
right={
|
||||
<RenderIframe
|
||||
frameClasses={'h-100 w-100'}
|
||||
markdownContent={ markdownContent }
|
||||
onMakeScrollSource={ setRendererToScrollSource }
|
||||
onFirstHeadingChange={ updateNoteTitleByFirstHeading }
|
||||
onTaskCheckedChange={ SetCheckboxInMarkdownContent }
|
||||
onFrontmatterChange={ setNoteFrontmatter }
|
||||
onScroll={ onMarkdownRendererScroll }
|
||||
scrollState={ scrollState.rendererScrollState }/>
|
||||
scrollState={ scrollState.rendererScrollState }
|
||||
rendererType={ RendererType.DOCUMENT }/>
|
||||
}
|
||||
containerClassName={ 'overflow-hidden' }/>
|
||||
<Sidebar/>
|
||||
|
|
|
@ -10,13 +10,20 @@ import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-a
|
|||
import { ApplicationState } from '../../../redux'
|
||||
import { isTestMode } from '../../../utils/is-test-mode'
|
||||
import { IframeEditorToRendererCommunicator } from '../../render-page/iframe-editor-to-renderer-communicator'
|
||||
import { MarkdownDocumentProps } from '../../render-page/markdown-document'
|
||||
import { ImageDetails } from '../../render-page/rendering-message'
|
||||
import { RendererProps } from '../../render-page/markdown-document'
|
||||
import { ImageDetails, RendererType } from '../../render-page/rendering-message'
|
||||
import { ScrollState } from '../synced-scroll/scroll-props'
|
||||
import { useOnIframeLoad } from './hooks/use-on-iframe-load'
|
||||
import { ShowOnPropChangeImageLightbox } from './show-on-prop-change-image-lightbox'
|
||||
|
||||
export const RenderIframe: React.FC<MarkdownDocumentProps> = (
|
||||
export interface RenderIframeProps extends RendererProps {
|
||||
onRendererReadyChange?: (rendererReady: boolean) => void
|
||||
rendererType: RendererType,
|
||||
forcedDarkMode?: boolean
|
||||
frameClasses?: string
|
||||
}
|
||||
|
||||
export const RenderIframe: React.FC<RenderIframeProps> = (
|
||||
{
|
||||
markdownContent,
|
||||
onTaskCheckedChange,
|
||||
|
@ -25,9 +32,13 @@ export const RenderIframe: React.FC<MarkdownDocumentProps> = (
|
|||
onFirstHeadingChange,
|
||||
onScroll,
|
||||
onMakeScrollSource,
|
||||
extraClasses
|
||||
frameClasses,
|
||||
onRendererReadyChange,
|
||||
rendererType,
|
||||
forcedDarkMode
|
||||
}) => {
|
||||
const darkMode = useIsDarkModeActivated()
|
||||
const savedDarkMode = useIsDarkModeActivated()
|
||||
const darkMode = forcedDarkMode ?? savedDarkMode
|
||||
const [rendererReady, setRendererReady] = useState<boolean>(false)
|
||||
const [lightboxDetails, setLightboxDetails] = useState<ImageDetails | undefined>(undefined)
|
||||
|
||||
|
@ -37,6 +48,11 @@ export const RenderIframe: React.FC<MarkdownDocumentProps> = (
|
|||
const resetRendererReady = useCallback(() => setRendererReady(false), [])
|
||||
const iframeCommunicator = useMemo(() => new IframeEditorToRendererCommunicator(), [])
|
||||
const onIframeLoad = useOnIframeLoad(frameReference, iframeCommunicator, rendererOrigin, renderPageUrl, resetRendererReady)
|
||||
const [frameHeight, setFrameHeight] = useState<number>(0)
|
||||
|
||||
useEffect(() => {
|
||||
onRendererReadyChange?.(rendererReady)
|
||||
}, [onRendererReadyChange, rendererReady])
|
||||
|
||||
useEffect(() => () => iframeCommunicator.unregisterEventListener(), [iframeCommunicator])
|
||||
useEffect(() => iframeCommunicator.onFirstHeadingChange(onFirstHeadingChange), [iframeCommunicator,
|
||||
|
@ -49,8 +65,15 @@ export const RenderIframe: React.FC<MarkdownDocumentProps> = (
|
|||
useEffect(() => iframeCommunicator.onTaskCheckboxChange(onTaskCheckedChange), [iframeCommunicator,
|
||||
onTaskCheckedChange])
|
||||
useEffect(() => iframeCommunicator.onImageClicked(setLightboxDetails), [iframeCommunicator])
|
||||
useEffect(() => iframeCommunicator.onRendererReady(() => setRendererReady(true)), [darkMode, iframeCommunicator,
|
||||
scrollState])
|
||||
useEffect(() => iframeCommunicator.onRendererReady(() => {
|
||||
iframeCommunicator.sendSetBaseConfiguration({
|
||||
baseUrl: window.location.toString(),
|
||||
rendererType
|
||||
})
|
||||
setRendererReady(true)
|
||||
}), [darkMode, rendererType, iframeCommunicator, rendererReady, scrollState])
|
||||
useEffect(() => iframeCommunicator.onHeightChange(setFrameHeight), [iframeCommunicator])
|
||||
|
||||
useEffect(() => {
|
||||
if (rendererReady) {
|
||||
iframeCommunicator.sendSetDarkmode(darkMode)
|
||||
|
@ -65,12 +88,6 @@ export const RenderIframe: React.FC<MarkdownDocumentProps> = (
|
|||
}
|
||||
}, [iframeCommunicator, rendererReady, scrollState])
|
||||
|
||||
useEffect(() => {
|
||||
if (rendererReady) {
|
||||
iframeCommunicator.sendSetBaseUrl(window.location.toString())
|
||||
}
|
||||
}, [iframeCommunicator, rendererReady])
|
||||
|
||||
useEffect(() => {
|
||||
if (rendererReady) {
|
||||
iframeCommunicator.sendSetMarkdownContent(markdownContent)
|
||||
|
@ -79,8 +96,9 @@ export const RenderIframe: React.FC<MarkdownDocumentProps> = (
|
|||
|
||||
return <Fragment>
|
||||
<ShowOnPropChangeImageLightbox details={ lightboxDetails }/>
|
||||
<iframe data-cy={ 'documentIframe' } onLoad={ onIframeLoad } title="render" src={ renderPageUrl }
|
||||
<iframe style={ { height: `${ frameHeight }px` } } data-cy={ 'documentIframe' } onLoad={ onIframeLoad }
|
||||
title="render" src={ renderPageUrl }
|
||||
{ ...isTestMode() ? {} : { sandbox: 'allow-downloads allow-same-origin allow-scripts allow-popups' } }
|
||||
ref={ frameReference } className={ `h-100 w-100 border-0 ${ extraClasses ?? '' }` }/>
|
||||
ref={ frameReference } className={ `border-0 ${ frameClasses ?? '' }` }/>
|
||||
</Fragment>
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
|
@ -38,6 +38,7 @@ export const CoverButtons: React.FC = () => {
|
|||
</ShowIf>
|
||||
<Link to="/n/features">
|
||||
<Button
|
||||
data-cy={ 'features-button' }
|
||||
className="cover-button"
|
||||
variant="primary"
|
||||
size="lg"
|
||||
|
|
|
@ -13,29 +13,29 @@ import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
|
|||
export const FeatureLinks: React.FC = () => {
|
||||
useTranslation()
|
||||
return (
|
||||
<Row className="mb-5">
|
||||
<Col md={ 4 }>
|
||||
<Row className="mb-5 d-flex flex-row justify-content-center">
|
||||
<Col md={ 3 }>
|
||||
<Link to={ '/n/features#Share-Notes' } className="text-light">
|
||||
<ForkAwesomeIcon icon="bolt" size="3x"/>
|
||||
<h5>
|
||||
<ForkAwesomeIcon icon="bolt" size="2x"/>
|
||||
<h6>
|
||||
<Trans i18nKey="landing.intro.features.collaboration"/>
|
||||
</h5>
|
||||
</h6>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col md={ 4 }>
|
||||
<Col md={ 3 }>
|
||||
<Link to={ '/n/features#MathJax' } className="text-light">
|
||||
<ForkAwesomeIcon icon="bar-chart" size="3x"/>
|
||||
<h5>
|
||||
<ForkAwesomeIcon icon="bar-chart" size="2x"/>
|
||||
<h6>
|
||||
<Trans i18nKey="landing.intro.features.katex"/>
|
||||
</h5>
|
||||
</h6>
|
||||
</Link>
|
||||
</Col>
|
||||
<Col md={ 4 }>
|
||||
<Col md={ 3 }>
|
||||
<Link to={ '/n/features#Slide-Mode' } className="text-light">
|
||||
<ForkAwesomeIcon icon="television" size="3x"/>
|
||||
<h5>
|
||||
<ForkAwesomeIcon icon="television" size="2x"/>
|
||||
<h6>
|
||||
<Trans i18nKey="landing.intro.features.slides"/>
|
||||
</h5>
|
||||
</h6>
|
||||
</Link>
|
||||
</Col>
|
||||
</Row>
|
||||
|
|
29
src/components/intro-page/hooks/use-intro-page-content.ts
Normal file
29
src/components/intro-page/hooks/use-intro-page-content.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { getFrontPageContent } from '../requests'
|
||||
import { useFrontendBaseUrl } from '../../../hooks/common/use-frontend-base-url'
|
||||
|
||||
const MARKDOWN_WHILE_LOADING = ':zzz: {message}'
|
||||
const MARKDOWN_IF_ERROR = ':::danger\n' +
|
||||
'{message}\n' +
|
||||
':::'
|
||||
|
||||
export const useIntroPageContent = (): string => {
|
||||
const { t } = useTranslation()
|
||||
const [content, setContent] = useState<string>(() => MARKDOWN_WHILE_LOADING.replace('{message}', t('landing.intro.markdownWhileLoading')))
|
||||
const frontendBaseUrl = useFrontendBaseUrl()
|
||||
|
||||
useEffect(() => {
|
||||
getFrontPageContent(frontendBaseUrl)
|
||||
.then((content) => setContent(content))
|
||||
.catch(() => setContent(MARKDOWN_IF_ERROR.replace('{message}', t('landing.intro.markdownLoadingError'))))
|
||||
}, [frontendBaseUrl, t])
|
||||
|
||||
return content
|
||||
}
|
Binary file not shown.
Before Width: | Height: | Size: 222 KiB |
|
@ -1,37 +1,54 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
*SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment } from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import React, { Fragment, useState } from 'react'
|
||||
import { Trans } from 'react-i18next'
|
||||
import { Branding } from '../common/branding/branding'
|
||||
import {
|
||||
HedgeDocLogoSize,
|
||||
HedgeDocLogoType,
|
||||
HedgeDocLogoWithText
|
||||
} from '../common/hedge-doc-logo/hedge-doc-logo-with-text'
|
||||
import { RenderIframe } from '../editor-page/renderer-pane/render-iframe'
|
||||
import { CoverButtons } from './cover-buttons/cover-buttons'
|
||||
import { FeatureLinks } from './feature-links'
|
||||
import screenshot from './img/screenshot.png'
|
||||
import { useIntroPageContent } from './hooks/use-intro-page-content'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
|
||||
import { RendererType } from '../render-page/rendering-message'
|
||||
|
||||
export const IntroPage: React.FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const introPageContent = useIntroPageContent()
|
||||
const [showSpinner, setShowSpinner] = useState<boolean>(true)
|
||||
|
||||
return <Fragment>
|
||||
<h1 dir='auto' className={ 'align-items-center d-flex justify-content-center flex-column' }>
|
||||
<HedgeDocLogoWithText logoType={ HedgeDocLogoType.COLOR_VERTICAL } size={ HedgeDocLogoSize.BIG }/>
|
||||
</h1>
|
||||
<p className="lead">
|
||||
<Trans i18nKey="app.slogan"/>
|
||||
</p>
|
||||
<div className={ 'mb-5' }>
|
||||
<Branding delimiter={ false }/>
|
||||
</div>
|
||||
|
||||
<CoverButtons/>
|
||||
<img alt={ t('landing.intro.screenShotAltText') } src={ screenshot } className="img-fluid mb-5"/>
|
||||
<FeatureLinks/>
|
||||
</Fragment>
|
||||
return (
|
||||
<Fragment>
|
||||
<div className={ 'flex-fill mt-3' }>
|
||||
<h1 dir='auto' className={ 'align-items-center d-flex justify-content-center flex-column' }>
|
||||
<HedgeDocLogoWithText logoType={ HedgeDocLogoType.COLOR_VERTICAL } size={ HedgeDocLogoSize.BIG }/>
|
||||
</h1>
|
||||
<p className="lead">
|
||||
<Trans i18nKey="app.slogan"/>
|
||||
</p>
|
||||
<div className={ 'mb-5' }>
|
||||
<Branding delimiter={ false }/>
|
||||
</div>
|
||||
<CoverButtons/>
|
||||
<ShowIf condition={ showSpinner }>
|
||||
<ForkAwesomeIcon icon={ 'spinner' } className={ 'fa-spin' }/>
|
||||
</ShowIf>
|
||||
<RenderIframe
|
||||
frameClasses={ 'w-100 overflow-y-hidden' }
|
||||
markdownContent={ introPageContent }
|
||||
disableToc={ true }
|
||||
onRendererReadyChange={ (rendererReady => setShowSpinner(!rendererReady)) }
|
||||
rendererType={ RendererType.INTRO }
|
||||
forcedDarkMode={ true }/>
|
||||
<hr className={ 'mb-5' }/>
|
||||
</div>
|
||||
<FeatureLinks/>
|
||||
</Fragment>)
|
||||
}
|
||||
|
|
17
src/components/intro-page/requests.ts
Normal file
17
src/components/intro-page/requests.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { defaultFetchConfig, expectResponseCode } from '../../api/utils'
|
||||
|
||||
export const getFrontPageContent = async (baseUrl: string): Promise<string> => {
|
||||
const response = await fetch(baseUrl + '/intro.md', {
|
||||
...defaultFetchConfig,
|
||||
method: 'GET'
|
||||
})
|
||||
expectResponseCode(response)
|
||||
|
||||
return await response.text()
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import equal from 'fast-deep-equal'
|
||||
|
@ -43,10 +43,10 @@ export const VersionInfo: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Fragment>
|
||||
<Link id='version' to={ '#' } className={ 'text-light' } onClick={ handleShow }>
|
||||
<Link data-cy={ 'show-version-modal' } to={ '#' } className={ 'text-light' } onClick={ handleShow }>
|
||||
<Trans i18nKey={ 'landing.versionInfo.versionInfo' }/>
|
||||
</Link>
|
||||
<Modal id='versionModal' show={ show } onHide={ handleClose } animation={ true }>
|
||||
<Modal data-cy={ 'version-modal' } show={ show } onHide={ handleClose } animation={ true }>
|
||||
<Modal.Body className="text-dark">
|
||||
<h3><Trans i18nKey={ 'landing.versionInfo.title' }/></h3>
|
||||
<Row>
|
||||
|
@ -55,7 +55,7 @@ export const VersionInfo: React.FC = () => {
|
|||
</Row>
|
||||
</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant="secondary" onClick={ handleClose }>
|
||||
<Button variant="secondary" onClick={ handleClose } data-cy={ 'close-version-modal-button' }>
|
||||
<Trans i18nKey={ 'common.close' }/>
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/*
|
||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
|
||||
SPDX-License-Identifier: AGPL-3.0-only
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React from 'react'
|
||||
|
@ -13,9 +13,7 @@ import { LinkContainer } from 'react-router-bootstrap'
|
|||
import { ApplicationState } from '../../../redux'
|
||||
import { ShowIf } from '../../common/show-if/show-if'
|
||||
|
||||
type SignInButtonProps = {
|
||||
className?: string
|
||||
} & Omit<ButtonProps, 'href'>
|
||||
export type SignInButtonProps = Omit<ButtonProps, 'href'>
|
||||
|
||||
export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props }) => {
|
||||
const { t } = useTranslation()
|
||||
|
@ -25,9 +23,9 @@ export const SignInButton: React.FC<SignInButtonProps> = ({ variant, ...props })
|
|||
<ShowIf condition={ anyAuthProviderActive }>
|
||||
<LinkContainer to="/login" title={ t('login.signIn') }>
|
||||
<Button
|
||||
data-cy={ 'sign-in-button' }
|
||||
variant={ variant || 'success' }
|
||||
{ ...props }
|
||||
>
|
||||
{ ...props }>
|
||||
<Trans i18nKey="login.signIn"/>
|
||||
</Button>
|
||||
</LinkContainer>
|
||||
|
|
|
@ -71,11 +71,13 @@ export const FullMarkdownRenderer: React.FC<FullMarkdownRendererProps & Addition
|
|||
toc => {
|
||||
tocAst.current = toc
|
||||
},
|
||||
lineMarkers => {
|
||||
currentLineMarkers.current = lineMarkers
|
||||
}
|
||||
onLineMarkerPositionChanged === undefined
|
||||
? undefined
|
||||
: lineMarkers => {
|
||||
currentLineMarkers.current = lineMarkers
|
||||
}
|
||||
)).buildConfiguredMarkdownIt()
|
||||
}, [onFrontmatterChange])
|
||||
}, [onLineMarkerPositionChanged, onFrontmatterChange])
|
||||
|
||||
const clearFrontmatter = useCallback(() => {
|
||||
hasNewYamlError.current = false
|
||||
|
|
|
@ -29,7 +29,7 @@ export class FullMarkdownItConfigurator extends BasicMarkdownItConfigurator {
|
|||
private passYamlErrorState: (error: boolean) => void,
|
||||
private onRawMeta: (rawMeta: RawNoteFrontmatter) => void,
|
||||
private onToc: (toc: TocAst) => void,
|
||||
private onLineMarkers: (lineMarkers: LineMarkers[]) => void
|
||||
private onLineMarkers?: (lineMarkers: LineMarkers[]) => void
|
||||
) {
|
||||
super()
|
||||
}
|
||||
|
@ -58,8 +58,12 @@ export class FullMarkdownItConfigurator extends BasicMarkdownItConfigurator {
|
|||
AsciinemaReplacer.markdownItPlugin,
|
||||
highlightedCode,
|
||||
quoteExtra,
|
||||
(markdownIt) => documentToc(markdownIt, this.onToc),
|
||||
(markdownIt) => lineNumberMarker(markdownIt, (lineMarkers) => this.onLineMarkers(lineMarkers))
|
||||
)
|
||||
(markdownIt) => documentToc(markdownIt, this.onToc))
|
||||
if (this.onLineMarkers) {
|
||||
const callback = this.onLineMarkers
|
||||
this.configurations.push(
|
||||
(markdownIt) => lineNumberMarker(markdownIt, (lineMarkers) => callback(lineMarkers))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
23
src/components/render-page/hooks/use-image-click-handler.ts
Normal file
23
src/components/render-page/hooks/use-image-click-handler.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react'
|
||||
import { ImageClickHandler } from '../../markdown-renderer/replace-components/image/image-replacer'
|
||||
import { IframeRendererToEditorCommunicator } from '../iframe-renderer-to-editor-communicator'
|
||||
|
||||
export const useImageClickHandler = (iframeCommunicator: IframeRendererToEditorCommunicator): ImageClickHandler => {
|
||||
return useCallback((event: React.MouseEvent<HTMLImageElement, MouseEvent>) => {
|
||||
const image = event.target as HTMLImageElement
|
||||
if (image.src === '') {
|
||||
return
|
||||
}
|
||||
iframeCommunicator.sendClickedImageUrl({
|
||||
src: image.src,
|
||||
alt: image.alt,
|
||||
title: image.title
|
||||
})
|
||||
}, [iframeCommunicator])
|
||||
}
|
|
@ -8,6 +8,7 @@ import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatte
|
|||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
||||
import { IframeCommunicator } from './iframe-communicator'
|
||||
import {
|
||||
BaseConfiguration,
|
||||
EditorToRendererIframeMessage,
|
||||
ImageDetails,
|
||||
RendererToEditorIframeMessage,
|
||||
|
@ -22,6 +23,11 @@ export class IframeEditorToRendererCommunicator extends IframeCommunicator<Edito
|
|||
private onSetScrollStateHandler?: (scrollState: ScrollState) => void
|
||||
private onRendererReadyHandler?: () => void
|
||||
private onImageClickedHandler?: (details: ImageDetails) => void
|
||||
private onHeightChangeHandler?: (height: number) => void
|
||||
|
||||
public onHeightChange(handler?: (height: number) => void): void {
|
||||
this.onHeightChangeHandler = handler
|
||||
}
|
||||
|
||||
public onFrontmatterChange(handler?: (frontmatter?: NoteFrontmatter) => void): void {
|
||||
this.onFrontmatterChangeHandler = handler
|
||||
|
@ -51,10 +57,10 @@ export class IframeEditorToRendererCommunicator extends IframeCommunicator<Edito
|
|||
this.onSetScrollStateHandler = handler
|
||||
}
|
||||
|
||||
public sendSetBaseUrl(baseUrl: string): void {
|
||||
public sendSetBaseConfiguration(baseConfiguration: BaseConfiguration): void {
|
||||
this.sendMessageToOtherSide({
|
||||
type: RenderIframeMessageType.SET_BASE_URL,
|
||||
baseUrl
|
||||
type: RenderIframeMessageType.SET_BASE_CONFIGURATION,
|
||||
baseConfiguration
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -106,6 +112,9 @@ export class IframeEditorToRendererCommunicator extends IframeCommunicator<Edito
|
|||
case RenderIframeMessageType.IMAGE_CLICKED:
|
||||
this.onImageClickedHandler?.(renderMessage.details)
|
||||
return false
|
||||
case RenderIframeMessageType.ON_HEIGHT_CHANGE:
|
||||
this.onHeightChangeHandler?.(renderMessage.height)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatte
|
|||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
||||
import { IframeCommunicator } from './iframe-communicator'
|
||||
import {
|
||||
BaseConfiguration,
|
||||
EditorToRendererIframeMessage,
|
||||
ImageDetails,
|
||||
RendererToEditorIframeMessage,
|
||||
|
@ -18,10 +19,10 @@ export class IframeRendererToEditorCommunicator extends IframeCommunicator<Rende
|
|||
private onSetMarkdownContentHandler?: ((markdownContent: string) => void)
|
||||
private onSetDarkModeHandler?: ((darkModeActivated: boolean) => void)
|
||||
private onSetScrollStateHandler?: ((scrollState: ScrollState) => void)
|
||||
private onSetBaseUrlHandler?: ((baseUrl: string) => void)
|
||||
private onSetBaseConfigurationHandler?: ((baseConfiguration: BaseConfiguration) => void)
|
||||
|
||||
public onSetBaseUrl(handler?: (baseUrl: string) => void): void {
|
||||
this.onSetBaseUrlHandler = handler
|
||||
public onSetBaseConfiguration(handler?: (baseConfiguration: BaseConfiguration) => void): void {
|
||||
this.onSetBaseConfigurationHandler = handler
|
||||
}
|
||||
|
||||
public onSetMarkdownContent(handler?: (markdownContent: string) => void): void {
|
||||
|
@ -84,6 +85,13 @@ export class IframeRendererToEditorCommunicator extends IframeCommunicator<Rende
|
|||
})
|
||||
}
|
||||
|
||||
public sendHeightChange(height: number): void {
|
||||
this.sendMessageToOtherSide({
|
||||
type: RenderIframeMessageType.ON_HEIGHT_CHANGE,
|
||||
height
|
||||
})
|
||||
}
|
||||
|
||||
protected handleEvent(event: MessageEvent<EditorToRendererIframeMessage>): boolean | undefined {
|
||||
const renderMessage = event.data
|
||||
switch (renderMessage.type) {
|
||||
|
@ -96,8 +104,8 @@ export class IframeRendererToEditorCommunicator extends IframeCommunicator<Rende
|
|||
case RenderIframeMessageType.SET_SCROLL_STATE:
|
||||
this.onSetScrollStateHandler?.(renderMessage.scrollState)
|
||||
return false
|
||||
case RenderIframeMessageType.SET_BASE_URL:
|
||||
this.onSetBaseUrlHandler?.(renderMessage.baseUrl)
|
||||
case RenderIframeMessageType.SET_BASE_CONFIGURATION:
|
||||
this.onSetBaseConfigurationHandler?.(renderMessage.baseConfiguration)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,8 +8,7 @@
|
|||
width: 100%;
|
||||
height: 100%;
|
||||
margin: 0;
|
||||
overflow-y: scroll;
|
||||
overflow-x: auto;
|
||||
overflow: auto;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
|
||||
import { TocAst } from 'markdown-it-toc-done-right'
|
||||
import React, { MutableRefObject, useMemo, useRef, useState } from 'react'
|
||||
import React, { MutableRefObject, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import { Dropdown } from 'react-bootstrap'
|
||||
import useResizeObserver from 'use-resize-observer'
|
||||
import { ForkAwesomeIcon } from '../common/fork-awesome/fork-awesome-icon'
|
||||
|
@ -19,8 +19,7 @@ import { FullMarkdownRenderer } from '../markdown-renderer/full-markdown-rendere
|
|||
import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer'
|
||||
import './markdown-document.scss'
|
||||
|
||||
export interface MarkdownDocumentProps extends ScrollProps {
|
||||
extraClasses?: string
|
||||
export interface RendererProps extends ScrollProps {
|
||||
onFirstHeadingChange?: (firstHeading: string | undefined) => void
|
||||
onFrontmatterChange?: (frontmatter: NoteFrontmatter | undefined) => void
|
||||
onTaskCheckedChange?: (lineInMarkdown: number, checked: boolean) => void
|
||||
|
@ -28,11 +27,19 @@ export interface MarkdownDocumentProps extends ScrollProps {
|
|||
markdownContent: string,
|
||||
baseUrl?: string
|
||||
onImageClick?: ImageClickHandler
|
||||
onHeightChange?: (height: number) => void
|
||||
disableToc?: boolean
|
||||
}
|
||||
|
||||
export interface MarkdownDocumentProps extends RendererProps {
|
||||
additionalOuterContainerClasses?: string
|
||||
additionalRendererClasses?: string
|
||||
}
|
||||
|
||||
export const MarkdownDocument: React.FC<MarkdownDocumentProps> = (
|
||||
{
|
||||
extraClasses,
|
||||
additionalOuterContainerClasses,
|
||||
additionalRendererClasses,
|
||||
onFirstHeadingChange,
|
||||
onFrontmatterChange,
|
||||
onMakeScrollSource,
|
||||
|
@ -41,42 +48,53 @@ export const MarkdownDocument: React.FC<MarkdownDocumentProps> = (
|
|||
markdownContent,
|
||||
onImageClick,
|
||||
onScroll,
|
||||
scrollState
|
||||
scrollState,
|
||||
onHeightChange,
|
||||
disableToc
|
||||
}) => {
|
||||
const rendererRef = useRef<HTMLDivElement | null>(null)
|
||||
const internalDocumentRenderPaneRef = useRef<HTMLDivElement>(null)
|
||||
const [tocAst, setTocAst] = useState<TocAst>()
|
||||
const width = useResizeObserver({ ref: internalDocumentRenderPaneRef.current }).width ?? 0
|
||||
|
||||
const internalDocumentRenderPaneSize = useResizeObserver({ ref: internalDocumentRenderPaneRef.current })
|
||||
const rendererSize = useResizeObserver({ ref: rendererRef.current })
|
||||
|
||||
const containerWidth = internalDocumentRenderPaneSize.width ?? 0
|
||||
|
||||
useEffect(() => {
|
||||
if (!onHeightChange) {
|
||||
return
|
||||
}
|
||||
onHeightChange(rendererSize.height ? rendererSize.height + 1 : 0)
|
||||
}, [rendererSize.height, onHeightChange])
|
||||
|
||||
const contentLineCount = useMemo(() => markdownContent.split('\n').length, [markdownContent])
|
||||
const [onLineMarkerPositionChanged, onUserScroll] = useSyncedScrolling(internalDocumentRenderPaneRef, rendererRef, contentLineCount, scrollState, onScroll)
|
||||
|
||||
return (
|
||||
<div className={ `markdown-document ${ extraClasses ?? '' }` }
|
||||
<div className={ `markdown-document ${ additionalOuterContainerClasses ?? '' }` }
|
||||
ref={ internalDocumentRenderPaneRef } onScroll={ onUserScroll } onMouseEnter={ onMakeScrollSource }>
|
||||
<div className={ 'markdown-document-side' }/>
|
||||
<div className={ 'bg-light markdown-document-content' }>
|
||||
<div className={ 'markdown-document-content' }>
|
||||
<YamlArrayDeprecationAlert/>
|
||||
<FullMarkdownRenderer
|
||||
rendererRef={ rendererRef }
|
||||
className={ 'flex-fill pt-4 mb-3' }
|
||||
className={ `flex-fill mb-3 ${ additionalRendererClasses ?? '' }` }
|
||||
content={ markdownContent }
|
||||
onFirstHeadingChange={ onFirstHeadingChange }
|
||||
onLineMarkerPositionChanged={ onLineMarkerPositionChanged }
|
||||
onFrontmatterChange={ onFrontmatterChange }
|
||||
onTaskCheckedChange={ onTaskCheckedChange }
|
||||
onTocChange={ (tocAst) => setTocAst(tocAst) }
|
||||
onTocChange={ setTocAst }
|
||||
baseUrl={ baseUrl }
|
||||
onImageClick={ onImageClick }/>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={ 'markdown-document-side pt-4' }>
|
||||
<ShowIf condition={ !!tocAst }>
|
||||
<ShowIf condition={ width >= 1100 }>
|
||||
<ShowIf condition={ !!tocAst && !disableToc }>
|
||||
<ShowIf condition={ containerWidth >= 1100 }>
|
||||
<TableOfContents ast={ tocAst as TocAst } className={ 'sticky' } baseUrl={ baseUrl }/>
|
||||
</ShowIf>
|
||||
<ShowIf condition={ width < 1100 }>
|
||||
<ShowIf condition={ containerWidth < 1100 }>
|
||||
<div className={ 'markdown-toc-sidebar-button' }>
|
||||
<Dropdown drop={ 'up' }>
|
||||
<Dropdown.Toggle id="toc-overlay-button" variant={ 'secondary' } className={ 'no-arrow' }>
|
||||
|
|
|
@ -12,15 +12,17 @@ import { setNoteFrontmatter } from '../../redux/note-details/methods'
|
|||
import { NoteFrontmatter } from '../editor-page/note-frontmatter/note-frontmatter'
|
||||
import { ScrollState } from '../editor-page/synced-scroll/scroll-props'
|
||||
import { ImageClickHandler } from '../markdown-renderer/replace-components/image/image-replacer'
|
||||
import { useImageClickHandler } from './hooks/use-image-click-handler'
|
||||
import { IframeRendererToEditorCommunicator } from './iframe-renderer-to-editor-communicator'
|
||||
import { MarkdownDocument } from './markdown-document'
|
||||
import { BaseConfiguration, RendererType } from './rendering-message'
|
||||
|
||||
export const RenderPage: React.FC = () => {
|
||||
useApplyDarkMode()
|
||||
|
||||
const [markdownContent, setMarkdownContent] = useState('')
|
||||
const [scrollState, setScrollState] = useState<ScrollState>({ firstLineInView: 1, scrolledPercentage: 0 })
|
||||
const [baseUrl, setBaseUrl] = useState<string>()
|
||||
const [baseConfiguration, setBaseConfiguration] = useState<BaseConfiguration | undefined>(undefined)
|
||||
|
||||
const editorOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.editorOrigin)
|
||||
|
||||
|
@ -35,7 +37,7 @@ export const RenderPage: React.FC = () => {
|
|||
return () => iframeCommunicator.unregisterEventListener()
|
||||
}, [iframeCommunicator])
|
||||
|
||||
useEffect(() => iframeCommunicator.onSetBaseUrl(setBaseUrl), [iframeCommunicator])
|
||||
useEffect(() => iframeCommunicator.onSetBaseConfiguration(setBaseConfiguration), [iframeCommunicator])
|
||||
useEffect(() => iframeCommunicator.onSetMarkdownContent(setMarkdownContent), [iframeCommunicator])
|
||||
useEffect(() => iframeCommunicator.onSetDarkMode(setDarkMode), [iframeCommunicator])
|
||||
useEffect(() => iframeCommunicator.onSetScrollState(setScrollState), [iframeCommunicator, scrollState])
|
||||
|
@ -61,37 +63,45 @@ export const RenderPage: React.FC = () => {
|
|||
iframeCommunicator.sendSetScrollState(scrollState)
|
||||
}, [iframeCommunicator])
|
||||
|
||||
const onImageClick: ImageClickHandler = useCallback((event) => {
|
||||
const image = event.target as HTMLImageElement
|
||||
if (image.src === '') {
|
||||
return
|
||||
}
|
||||
iframeCommunicator.sendClickedImageUrl({
|
||||
src: image.src,
|
||||
alt: image.alt,
|
||||
title: image.title
|
||||
})
|
||||
const onImageClick: ImageClickHandler = useImageClickHandler(iframeCommunicator)
|
||||
|
||||
const onHeightChange = useCallback((height: number) => {
|
||||
iframeCommunicator.sendHeightChange(height)
|
||||
}, [iframeCommunicator])
|
||||
|
||||
if (!baseUrl) {
|
||||
if (!baseConfiguration) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={ 'vh-100 w-100' }>
|
||||
<MarkdownDocument
|
||||
extraClasses={ 'bg-light' }
|
||||
markdownContent={ markdownContent }
|
||||
onTaskCheckedChange={ onTaskCheckedChange }
|
||||
onFirstHeadingChange={ onFirstHeadingChange }
|
||||
onMakeScrollSource={ onMakeScrollSource }
|
||||
onFrontmatterChange={ onFrontmatterChange }
|
||||
scrollState={ scrollState }
|
||||
onScroll={ onScroll }
|
||||
baseUrl={ baseUrl }
|
||||
onImageClick={ onImageClick }/>
|
||||
</div>
|
||||
)
|
||||
switch (baseConfiguration.rendererType) {
|
||||
case RendererType.DOCUMENT:
|
||||
return (
|
||||
<MarkdownDocument
|
||||
additionalOuterContainerClasses={ 'vh-100 bg-light' }
|
||||
additionalRendererClasses={ 'mb-3' }
|
||||
markdownContent={ markdownContent }
|
||||
onTaskCheckedChange={ onTaskCheckedChange }
|
||||
onFirstHeadingChange={ onFirstHeadingChange }
|
||||
onMakeScrollSource={ onMakeScrollSource }
|
||||
onFrontmatterChange={ onFrontmatterChange }
|
||||
scrollState={ scrollState }
|
||||
onScroll={ onScroll }
|
||||
baseUrl={ baseConfiguration.baseUrl }
|
||||
onImageClick={ onImageClick }/>
|
||||
)
|
||||
case RendererType.INTRO:
|
||||
return (
|
||||
<MarkdownDocument
|
||||
additionalOuterContainerClasses={ 'vh-100 bg-light overflow-y-hidden' }
|
||||
markdownContent={ markdownContent }
|
||||
baseUrl={ baseConfiguration.baseUrl }
|
||||
onImageClick={ onImageClick }
|
||||
disableToc={ true }
|
||||
onHeightChange={ onHeightChange }/>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
export default RenderPage
|
||||
|
|
|
@ -16,7 +16,8 @@ export enum RenderIframeMessageType {
|
|||
SET_SCROLL_STATE = 'SET_SCROLL_STATE',
|
||||
ON_SET_FRONTMATTER = 'ON_SET_FRONTMATTER',
|
||||
IMAGE_CLICKED = 'IMAGE_CLICKED',
|
||||
SET_BASE_URL = 'SET_BASE_URL'
|
||||
ON_HEIGHT_CHANGE = 'ON_HEIGHT_CHANGE',
|
||||
SET_BASE_CONFIGURATION = 'SET_BASE_CONFIGURATION'
|
||||
}
|
||||
|
||||
export interface RendererToEditorSimpleMessage {
|
||||
|
@ -35,8 +36,8 @@ export interface ImageDetails {
|
|||
}
|
||||
|
||||
export interface SetBaseUrlMessage {
|
||||
type: RenderIframeMessageType.SET_BASE_URL,
|
||||
baseUrl: string
|
||||
type: RenderIframeMessageType.SET_BASE_CONFIGURATION,
|
||||
baseConfiguration: BaseConfiguration
|
||||
}
|
||||
|
||||
export interface ImageClickedMessage {
|
||||
|
@ -70,6 +71,11 @@ export interface OnFrontmatterChangeMessage {
|
|||
frontmatter: NoteFrontmatter | undefined
|
||||
}
|
||||
|
||||
export interface OnHeightChangeMessage {
|
||||
type: RenderIframeMessageType.ON_HEIGHT_CHANGE,
|
||||
height: number
|
||||
}
|
||||
|
||||
export type EditorToRendererIframeMessage =
|
||||
SetMarkdownContentMessage |
|
||||
SetDarkModeMessage |
|
||||
|
@ -82,4 +88,15 @@ export type RendererToEditorIframeMessage =
|
|||
OnTaskCheckboxChangeMessage |
|
||||
OnFrontmatterChangeMessage |
|
||||
SetScrollStateMessage |
|
||||
ImageClickedMessage
|
||||
ImageClickedMessage |
|
||||
OnHeightChangeMessage
|
||||
|
||||
export enum RendererType {
|
||||
DOCUMENT,
|
||||
INTRO
|
||||
}
|
||||
|
||||
export interface BaseConfiguration {
|
||||
baseUrl: string
|
||||
rendererType: RendererType
|
||||
}
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
}
|
||||
|
||||
body {
|
||||
background-color: darken($dark, 8%);
|
||||
background-color: $dark;
|
||||
}
|
||||
|
||||
html {
|
||||
|
@ -87,3 +87,7 @@ body {
|
|||
.overflow-x-auto {
|
||||
overflow-x: auto !important;
|
||||
}
|
||||
|
||||
.overflow-y-hidden {
|
||||
overflow-y: hidden !important;
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
$blue: #337ab7 !default;
|
||||
$cyan: #5EB7E0 !default;
|
||||
$dark: #222222 !default;
|
||||
|
||||
@import "../../node_modules/bootstrap/scss/functions";
|
||||
@import "../../node_modules/bootstrap/scss/mixins";
|
||||
|
|
Loading…
Add table
Reference in a new issue