mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-23 19:47:03 -04:00
Change motd banner to motd modal
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
328bc917eb
commit
ee7cde0096
26 changed files with 361 additions and 269 deletions
|
@ -1,54 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setBanner } from '../../../redux/banner/methods'
|
||||
import { defaultFetchConfig } from '../../../api/utils'
|
||||
import { Logger } from '../../../utils/logger'
|
||||
|
||||
export const BANNER_LOCAL_STORAGE_KEY = 'banner.lastModified'
|
||||
const log = new Logger('Banner')
|
||||
|
||||
export const fetchAndSetBanner = async (customizeAssetsUrl: string): Promise<void> => {
|
||||
const cachedLastModified = window.localStorage.getItem(BANNER_LOCAL_STORAGE_KEY)
|
||||
const bannerUrl = `${customizeAssetsUrl}banner.txt`
|
||||
|
||||
if (cachedLastModified) {
|
||||
const response = await fetch(bannerUrl, {
|
||||
...defaultFetchConfig,
|
||||
method: 'HEAD'
|
||||
})
|
||||
if (response.status !== 200) {
|
||||
return
|
||||
}
|
||||
if (response.headers.get('Last-Modified') === cachedLastModified) {
|
||||
setBanner({
|
||||
lastModified: cachedLastModified,
|
||||
text: ''
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(bannerUrl, {
|
||||
...defaultFetchConfig
|
||||
})
|
||||
|
||||
if (response.status !== 200) {
|
||||
return
|
||||
}
|
||||
|
||||
const bannerText = await response.text()
|
||||
|
||||
const lastModified = response.headers.get('Last-Modified')
|
||||
if (!lastModified) {
|
||||
log.warn("'Last-Modified' not found for banner.txt!")
|
||||
}
|
||||
|
||||
setBanner({
|
||||
lastModified: lastModified,
|
||||
text: bannerText
|
||||
})
|
||||
}
|
56
src/components/application-loader/initializers/fetch-motd.ts
Normal file
56
src/components/application-loader/initializers/fetch-motd.ts
Normal file
|
@ -0,0 +1,56 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { setMotd } from '../../../redux/motd/methods'
|
||||
import { defaultFetchConfig } from '../../../api/utils'
|
||||
import { Logger } from '../../../utils/logger'
|
||||
|
||||
export const MOTD_LOCAL_STORAGE_KEY = 'motd.lastModified'
|
||||
const log = new Logger('Motd')
|
||||
|
||||
/**
|
||||
* Fetches the current motd from the backend and sets the content in the global application state.
|
||||
* If the motd hasn't changed since the last time then the global application state won't be changed.
|
||||
* To check if the motd has changed the "last modified" header from the request
|
||||
* will be compared to the saved value from the browser's local storage.
|
||||
*
|
||||
* @param customizeAssetsUrl the URL where the motd.txt can be found.
|
||||
* @return A promise that gets resolved if the motd was fetched successfully.
|
||||
*/
|
||||
export const fetchMotd = async (customizeAssetsUrl: string): Promise<void> => {
|
||||
const cachedLastModified = window.localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)
|
||||
const motdUrl = `${customizeAssetsUrl}motd.txt`
|
||||
|
||||
if (cachedLastModified) {
|
||||
const response = await fetch(motdUrl, {
|
||||
...defaultFetchConfig,
|
||||
method: 'HEAD'
|
||||
})
|
||||
if (response.status !== 200) {
|
||||
return
|
||||
}
|
||||
if (response.headers.get('Last-Modified') === cachedLastModified) {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(motdUrl, {
|
||||
...defaultFetchConfig
|
||||
})
|
||||
|
||||
if (response.status !== 200) {
|
||||
return
|
||||
}
|
||||
|
||||
const motdText = await response.text()
|
||||
|
||||
const lastModified = response.headers.get('Last-Modified')
|
||||
if (!lastModified) {
|
||||
log.warn("'Last-Modified' not found for motd.txt!")
|
||||
}
|
||||
|
||||
setMotd(motdText, lastModified)
|
||||
}
|
|
@ -6,7 +6,7 @@
|
|||
|
||||
import { setUpI18n } from './i18n/i18n'
|
||||
import { refreshHistoryState } from '../../../redux/history/methods'
|
||||
import { fetchAndSetBanner } from './fetch-and-set-banner'
|
||||
import { fetchMotd } from './fetch-motd'
|
||||
import { setApiUrl } from '../../../redux/api-url/methods'
|
||||
import { fetchAndSetUser } from '../../login-page/auth/utils'
|
||||
import { fetchFrontendConfig } from './fetch-frontend-config'
|
||||
|
@ -47,8 +47,8 @@ export const createSetUpTaskList = (
|
|||
task: fetchAndSetUser()
|
||||
},
|
||||
{
|
||||
name: 'Banner',
|
||||
task: fetchAndSetBanner(customizeAssetsUrl)
|
||||
name: 'Motd',
|
||||
task: fetchMotd(customizeAssetsUrl)
|
||||
},
|
||||
{
|
||||
name: 'Load history state',
|
||||
|
|
|
@ -13,7 +13,7 @@ import { ShowIf } from '../show-if/show-if'
|
|||
|
||||
export interface CommonModalProps {
|
||||
show: boolean
|
||||
onHide: () => void
|
||||
onHide?: () => void
|
||||
titleI18nKey?: string
|
||||
title?: string
|
||||
closeButton?: boolean
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { useCallback } from 'react'
|
||||
import { Alert, Button } from 'react-bootstrap'
|
||||
import { setBanner } from '../../../redux/banner/methods'
|
||||
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
|
||||
import { BANNER_LOCAL_STORAGE_KEY } from '../../application-loader/initializers/fetch-and-set-banner'
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
|
||||
export const MotdBanner: React.FC = () => {
|
||||
const bannerState = useApplicationState((state) => state.banner)
|
||||
|
||||
const dismissBanner = useCallback(() => {
|
||||
if (bannerState.lastModified) {
|
||||
window.localStorage.setItem(BANNER_LOCAL_STORAGE_KEY, bannerState.lastModified)
|
||||
}
|
||||
setBanner({
|
||||
text: '',
|
||||
lastModified: null
|
||||
})
|
||||
}, [bannerState])
|
||||
|
||||
if (bannerState.text === undefined) {
|
||||
return null
|
||||
}
|
||||
|
||||
if (!bannerState.text) {
|
||||
return <span data-cy={'no-motd-banner'} />
|
||||
}
|
||||
|
||||
return (
|
||||
<Alert
|
||||
data-cy={'motd-banner'}
|
||||
variant='primary'
|
||||
dir='auto'
|
||||
className='mb-0 text-center d-flex flex-row justify-content-center'>
|
||||
<span className='flex-grow-1 align-self-center text-black'>{bannerState.text}</span>
|
||||
<Button data-cy={'motd-dismiss'} variant='outline-primary' size='sm' className='mx-2' onClick={dismissBanner}>
|
||||
<ForkAwesomeIcon icon='times' />
|
||||
</Button>
|
||||
</Alert>
|
||||
)
|
||||
}
|
60
src/components/common/motd-modal/motd-modal.tsx
Normal file
60
src/components/common/motd-modal/motd-modal.tsx
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import React, { Fragment, useCallback, useMemo } from 'react'
|
||||
import { Button, Modal } from 'react-bootstrap'
|
||||
import { CommonModal } from '../modals/common-modal'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||
import { dismissMotd } from '../../../redux/motd/methods'
|
||||
|
||||
/**
|
||||
* Reads the motd from the global application state and shows it in a modal.
|
||||
* If the modal gets dismissed by the user then the "last modified" identifier will be written into the local storage
|
||||
* to prevent that the motd will be shown again until it gets changed.
|
||||
*/
|
||||
export const MotdModal: React.FC = () => {
|
||||
useTranslation()
|
||||
const motdState = useApplicationState((state) => state.motd)
|
||||
|
||||
const domContent = useMemo(() => {
|
||||
if (!motdState) {
|
||||
return null
|
||||
}
|
||||
return motdState.text
|
||||
?.split('\n')
|
||||
.map((line) => <span>{line}</span>)
|
||||
.reduce((previousLine, currentLine, currentLineIndex) => (
|
||||
<Fragment key={currentLineIndex}>
|
||||
{previousLine}
|
||||
<br />
|
||||
{currentLine}
|
||||
</Fragment>
|
||||
))
|
||||
}, [motdState])
|
||||
|
||||
const dismiss = useCallback(() => {
|
||||
if (!motdState) {
|
||||
return
|
||||
}
|
||||
dismissMotd()
|
||||
}, [motdState])
|
||||
|
||||
if (motdState === null || motdState.dismissed) {
|
||||
return null
|
||||
} else {
|
||||
return (
|
||||
<CommonModal data-cy={'motd'} show={!!motdState} titleI18nKey={'motd.title'}>
|
||||
<Modal.Body>{domContent}</Modal.Body>
|
||||
<Modal.Footer>
|
||||
<Button variant={'success'} onClick={dismiss} data-cy={'motd-dismiss'}>
|
||||
<Trans i18nKey={'common.dismiss'} />
|
||||
</Button>
|
||||
</Modal.Footer>
|
||||
</CommonModal>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import { useParams } from 'react-router'
|
|||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||
import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title'
|
||||
import { updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||
import { MotdModal } from '../common/motd-modal/motd-modal'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import { AppBar, AppBarMode } from '../editor-page/app-bar/app-bar'
|
||||
import { EditorPagePathParams } from '../editor-page/editor-page'
|
||||
|
@ -24,6 +24,7 @@ import { useApplicationState } from '../../hooks/common/use-application-state'
|
|||
import { useNoteMarkdownContentWithoutFrontmatter } from '../../hooks/common/use-note-markdown-content-without-frontmatter'
|
||||
import { EditorToRendererCommunicatorContextProvider } from '../editor-page/render-context/editor-to-renderer-communicator-context-provider'
|
||||
import { useSendFrontmatterInfoFromReduxToRenderer } from '../editor-page/renderer-pane/hooks/use-send-frontmatter-info-from-redux-to-renderer'
|
||||
import { UiNotifications } from '../notifications/ui-notifications'
|
||||
|
||||
export const DocumentReadOnlyPage: React.FC = () => {
|
||||
useTranslation()
|
||||
|
@ -40,8 +41,9 @@ export const DocumentReadOnlyPage: React.FC = () => {
|
|||
|
||||
return (
|
||||
<EditorToRendererCommunicatorContextProvider>
|
||||
<UiNotifications />
|
||||
<MotdModal />
|
||||
<div className={'d-flex flex-column mvh-100 bg-light'}>
|
||||
<MotdBanner />
|
||||
<AppBar mode={AppBarMode.BASIC} />
|
||||
<div className={'container'}>
|
||||
<ErrorWhileLoadingNoteAlert show={error} />
|
||||
|
|
|
@ -9,7 +9,7 @@ import { useTranslation } from 'react-i18next'
|
|||
import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode'
|
||||
import { useDocumentTitleWithNoteTitle } from '../../hooks/common/use-document-title-with-note-title'
|
||||
import { setCheckboxInMarkdownContent, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods'
|
||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||
import { MotdModal } from '../common/motd-modal/motd-modal'
|
||||
import { ShowIf } from '../common/show-if/show-if'
|
||||
import { ErrorWhileLoadingNoteAlert } from '../document-read-only-page/ErrorWhileLoadingNoteAlert'
|
||||
import { LoadingNoteAlert } from '../document-read-only-page/LoadingNoteAlert'
|
||||
|
@ -126,7 +126,7 @@ export const EditorPage: React.FC = () => {
|
|||
return (
|
||||
<EditorToRendererCommunicatorContextProvider>
|
||||
<UiNotifications />
|
||||
<MotdBanner />
|
||||
<MotdModal />
|
||||
<div className={'d-flex flex-column vh-100'}>
|
||||
<AppBar mode={AppBarMode.EDITOR} />
|
||||
<div className={'container'}>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import React, { Fragment } from 'react'
|
||||
import { Container } from 'react-bootstrap'
|
||||
import { useDocumentTitle } from '../../hooks/common/use-document-title'
|
||||
import { MotdBanner } from '../common/motd-banner/motd-banner'
|
||||
import { MotdModal } from '../common/motd-modal/motd-modal'
|
||||
import { Footer } from './footer/footer'
|
||||
import { HeaderBar } from './navigation/header-bar/header-bar'
|
||||
import { UiNotifications } from '../notifications/ui-notifications'
|
||||
|
@ -18,8 +18,8 @@ export const LandingLayout: React.FC = ({ children }) => {
|
|||
return (
|
||||
<Fragment>
|
||||
<UiNotifications />
|
||||
<MotdModal />
|
||||
<Container className='text-light d-flex flex-column mvh-100'>
|
||||
<MotdBanner />
|
||||
<HeaderBar />
|
||||
<div className={'d-flex flex-column justify-content-between flex-fill text-center'}>
|
||||
<main>{children}</main>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue