mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 15:14:56 -04:00
Adds an info banner to the app (#190)
* added info-banner component to show the banner.text, we got from the backend config. This banner is shown on top of the landing page (intro, history, login/signup and profile) and also on top of the editor and links to `/n/banner` * added banner to backendConfig Redux state * added BannerState to the ApplicationState with that the showing of the banner is globally controlled, the banner text is given to the banner component and the timestamp to acknowledge a banner was read by the user * the timestamp of a dismissed note is saved in the browsers localStorage to determine in the future if the banner should be shown Signed-off-by: Philip Molares <philip.molares@udo.edu> Co-authored-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
75aa8b38af
commit
e014eb36b5
13 changed files with 144 additions and 15 deletions
|
@ -13,6 +13,10 @@
|
||||||
"email": true,
|
"email": true,
|
||||||
"openid": true
|
"openid": true
|
||||||
},
|
},
|
||||||
|
"banner": {
|
||||||
|
"text": "This is the test banner text",
|
||||||
|
"timestamp": "2020-05-22T20:46:08.962Z"
|
||||||
|
},
|
||||||
"customAuthNames": {
|
"customAuthNames": {
|
||||||
"ldap": "FooBar",
|
"ldap": "FooBar",
|
||||||
"oauth2": "Olaf2",
|
"oauth2": "Olaf2",
|
||||||
|
|
14
public/api/v2/notes/banner
Normal file
14
public/api/v2/notes/banner
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"id": "ABC123",
|
||||||
|
"alias": "banner",
|
||||||
|
"lastChange": {
|
||||||
|
"userId": "snskxnksnxksnxksnx",
|
||||||
|
"username": "testy",
|
||||||
|
"timestamp": 123456789
|
||||||
|
},
|
||||||
|
"viewcount": 0,
|
||||||
|
"createtime": "2020-05-22T20:46:08.962Z",
|
||||||
|
"content": "This is the test banner text",
|
||||||
|
"authorship": [],
|
||||||
|
"preVersionTwoNote": true
|
||||||
|
}
|
|
@ -10,5 +10,5 @@
|
||||||
"createtime": "2020-05-22T20:46:08.962Z",
|
"createtime": "2020-05-22T20:46:08.962Z",
|
||||||
"content": "test123",
|
"content": "test123",
|
||||||
"authorship": [],
|
"authorship": [],
|
||||||
"preVersionTwoNote": true
|
"preVersionTwoNote": false
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,17 @@
|
||||||
export interface BackendConfig {
|
export interface BackendConfig {
|
||||||
allowAnonymous: boolean,
|
allowAnonymous: boolean,
|
||||||
authProviders: AuthProvidersState,
|
authProviders: AuthProvidersState,
|
||||||
|
banner: BannerConfig,
|
||||||
customAuthNames: CustomAuthNames,
|
customAuthNames: CustomAuthNames,
|
||||||
specialLinks: SpecialLinks,
|
specialLinks: SpecialLinks,
|
||||||
version: BackendVersion,
|
version: BackendVersion,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface BannerConfig {
|
||||||
|
text: string
|
||||||
|
timestamp: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface BackendVersion {
|
export interface BackendVersion {
|
||||||
version: string,
|
version: string,
|
||||||
sourceCodeUrl: string
|
sourceCodeUrl: string
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { getBackendConfig } from '../../../api/backend-config'
|
import { getBackendConfig } from '../../../api/backend-config'
|
||||||
import { getFrontendConfig } from '../../../api/frontend-config'
|
import { getFrontendConfig } from '../../../api/frontend-config'
|
||||||
import { setBackendConfig } from '../../../redux/backend-config/methods'
|
import { setBackendConfig } from '../../../redux/backend-config/methods'
|
||||||
|
import { setBanner } from '../../../redux/banner/methods'
|
||||||
import { setFrontendConfig } from '../../../redux/frontend-config/methods'
|
import { setFrontendConfig } from '../../../redux/frontend-config/methods'
|
||||||
import { getAndSetUser } from '../../../utils/apiUtils'
|
import { getAndSetUser } from '../../../utils/apiUtils'
|
||||||
|
|
||||||
|
@ -17,5 +18,14 @@ export const loadAllConfig: (baseUrl: string) => Promise<void> = async (baseUrl)
|
||||||
}
|
}
|
||||||
setBackendConfig(backendConfig)
|
setBackendConfig(backendConfig)
|
||||||
|
|
||||||
|
const banner = backendConfig.banner
|
||||||
|
if (banner.text !== '') {
|
||||||
|
const lastAcknowledgedTimestamp = window.localStorage.getItem('bannerTimeStamp') || ''
|
||||||
|
setBanner({
|
||||||
|
...banner,
|
||||||
|
show: banner.text !== '' && banner.timestamp !== lastAcknowledgedTimestamp
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
await getAndSetUser()
|
await getAndSetUser()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,18 +1,15 @@
|
||||||
import React, { useEffect, useState } from 'react'
|
import React, { Fragment, useEffect, useState } from 'react'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import useMedia from 'use-media'
|
import useMedia from 'use-media'
|
||||||
import { ApplicationState } from '../../redux'
|
import { ApplicationState } from '../../redux'
|
||||||
import { setEditorModeConfig } from '../../redux/editor/methods'
|
import { setEditorModeConfig } from '../../redux/editor/methods'
|
||||||
import { Splitter } from '../common/splitter/splitter'
|
import { Splitter } from '../common/splitter/splitter'
|
||||||
|
import { InfoBanner } from '../landing/layout/info-banner'
|
||||||
import { EditorWindow } from './editor-window/editor-window'
|
import { EditorWindow } from './editor-window/editor-window'
|
||||||
import { MarkdownPreview } from './markdown-preview/markdown-preview'
|
import { MarkdownPreview } from './markdown-preview/markdown-preview'
|
||||||
import { EditorMode } from './task-bar/editor-view-mode'
|
import { EditorMode } from './task-bar/editor-view-mode'
|
||||||
import { TaskBar } from './task-bar/task-bar'
|
import { TaskBar } from './task-bar/task-bar'
|
||||||
|
|
||||||
interface RouteParameters {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const Editor: React.FC = () => {
|
const Editor: React.FC = () => {
|
||||||
const editorMode: EditorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
|
const editorMode: EditorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode)
|
||||||
const isWide = useMedia({ minWidth: 576 })
|
const isWide = useMedia({ minWidth: 576 })
|
||||||
|
@ -29,15 +26,18 @@ const Editor: React.FC = () => {
|
||||||
}, [editorMode, firstDraw, isWide])
|
}, [editorMode, firstDraw, isWide])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={'d-flex flex-column vh-100'}>
|
<Fragment>
|
||||||
<TaskBar/>
|
<InfoBanner/>
|
||||||
<Splitter
|
<div className={'d-flex flex-column vh-100'}>
|
||||||
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
<TaskBar/>
|
||||||
left={<EditorWindow/>}
|
<Splitter
|
||||||
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
showLeft={editorMode === EditorMode.EDITOR || editorMode === EditorMode.BOTH}
|
||||||
right={<MarkdownPreview/>}
|
left={<EditorWindow/>}
|
||||||
containerClassName={'overflow-hidden'}/>
|
showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)}
|
||||||
</div>
|
right={<MarkdownPreview/>}
|
||||||
|
containerClassName={'overflow-hidden'}/>
|
||||||
|
</div>
|
||||||
|
</Fragment>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,11 +1,13 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Container } from 'react-bootstrap'
|
import { Container } from 'react-bootstrap'
|
||||||
import { Footer } from './layout/footer/footer'
|
import { Footer } from './layout/footer/footer'
|
||||||
|
import { InfoBanner } from './layout/info-banner'
|
||||||
import { HeaderBar } from './layout/navigation/header-bar/header-bar'
|
import { HeaderBar } from './layout/navigation/header-bar/header-bar'
|
||||||
|
|
||||||
export const LandingLayout: React.FC = ({ children }) => {
|
export const LandingLayout: React.FC = ({ children }) => {
|
||||||
return (
|
return (
|
||||||
<Container className="text-white d-flex flex-column mvh-100">
|
<Container className="text-white d-flex flex-column mvh-100">
|
||||||
|
<InfoBanner/>
|
||||||
<HeaderBar/>
|
<HeaderBar/>
|
||||||
<div className={'d-flex flex-column justify-content-between flex-fill text-center'}>
|
<div className={'d-flex flex-column justify-content-between flex-fill text-center'}>
|
||||||
<div>
|
<div>
|
||||||
|
|
34
src/components/landing/layout/info-banner.tsx
Normal file
34
src/components/landing/layout/info-banner.tsx
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { useSelector } from 'react-redux'
|
||||||
|
import { Link } from 'react-router-dom'
|
||||||
|
import { ApplicationState } from '../../../redux'
|
||||||
|
import { Alert, Button } from 'react-bootstrap'
|
||||||
|
import { setBanner } from '../../../redux/banner/methods'
|
||||||
|
import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon'
|
||||||
|
import { ShowIf } from '../../common/show-if/show-if'
|
||||||
|
|
||||||
|
export const InfoBanner: React.FC = () => {
|
||||||
|
const bannerState = useSelector((state: ApplicationState) => state.banner)
|
||||||
|
|
||||||
|
const dismissBanner = () => {
|
||||||
|
setBanner({ ...bannerState, show: false })
|
||||||
|
window.localStorage.setItem('bannerTimeStamp', bannerState.timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ShowIf condition={bannerState.show}>
|
||||||
|
<Alert variant='primary' dir='auto' className='mb-0 text-center d-flex flex-row justify-content-center'>
|
||||||
|
<Link to='/s/banner' className='flex-grow-1 align-self-center'>
|
||||||
|
{bannerState.text}
|
||||||
|
</Link>
|
||||||
|
<Button
|
||||||
|
variant='outline-primary'
|
||||||
|
size='sm'
|
||||||
|
className='mx-2'
|
||||||
|
onClick={dismissBanner}>
|
||||||
|
<ForkAwesomeIcon icon='times'/>
|
||||||
|
</Button>
|
||||||
|
</Alert>
|
||||||
|
</ShowIf>
|
||||||
|
)
|
||||||
|
}
|
|
@ -17,6 +17,10 @@ export const initialState: BackendConfig = {
|
||||||
email: false,
|
email: false,
|
||||||
openid: false
|
openid: false
|
||||||
},
|
},
|
||||||
|
banner: {
|
||||||
|
text: '',
|
||||||
|
timestamp: ''
|
||||||
|
},
|
||||||
customAuthNames: {
|
customAuthNames: {
|
||||||
ldap: '',
|
ldap: '',
|
||||||
oauth2: '',
|
oauth2: '',
|
||||||
|
|
10
src/redux/banner/methods.ts
Normal file
10
src/redux/banner/methods.ts
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import { store } from '../../utils/store'
|
||||||
|
import { BannerActionType, BannerState, SetBannerAction } from './types'
|
||||||
|
|
||||||
|
export const setBanner = (state: BannerState): void => {
|
||||||
|
const action: SetBannerAction = {
|
||||||
|
type: BannerActionType.SET_BANNER,
|
||||||
|
state
|
||||||
|
}
|
||||||
|
store.dispatch(action)
|
||||||
|
}
|
22
src/redux/banner/reducers.ts
Normal file
22
src/redux/banner/reducers.ts
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
import { Reducer } from 'redux'
|
||||||
|
import {
|
||||||
|
BannerActions,
|
||||||
|
BannerActionType,
|
||||||
|
BannerState,
|
||||||
|
SetBannerAction
|
||||||
|
} from './types'
|
||||||
|
|
||||||
|
export const initialState: BannerState = {
|
||||||
|
show: true,
|
||||||
|
text: '',
|
||||||
|
timestamp: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
export const BannerReducer: Reducer<BannerState, BannerActions> = (state: BannerState = initialState, action: BannerActions) => {
|
||||||
|
switch (action.type) {
|
||||||
|
case BannerActionType.SET_BANNER:
|
||||||
|
return (action as SetBannerAction).state
|
||||||
|
default:
|
||||||
|
return state
|
||||||
|
}
|
||||||
|
}
|
19
src/redux/banner/types.ts
Normal file
19
src/redux/banner/types.ts
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
import { Action } from 'redux'
|
||||||
|
|
||||||
|
export enum BannerActionType {
|
||||||
|
SET_BANNER = 'banner/set',
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BannerActions extends Action<BannerActionType> {
|
||||||
|
type: BannerActionType;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface SetBannerAction extends BannerActions {
|
||||||
|
state: BannerState;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BannerState {
|
||||||
|
show: boolean
|
||||||
|
text: string
|
||||||
|
timestamp: string
|
||||||
|
}
|
|
@ -2,6 +2,8 @@ import { combineReducers, Reducer } from 'redux'
|
||||||
import { BackendConfig } from '../api/backend-config/types'
|
import { BackendConfig } from '../api/backend-config/types'
|
||||||
import { FrontendConfig } from '../api/frontend-config/types'
|
import { FrontendConfig } from '../api/frontend-config/types'
|
||||||
import { BackendConfigReducer } from './backend-config/reducers'
|
import { BackendConfigReducer } from './backend-config/reducers'
|
||||||
|
import { BannerReducer } from './banner/reducers'
|
||||||
|
import { BannerState } from './banner/types'
|
||||||
import { EditorConfigReducer } from './editor/reducers'
|
import { EditorConfigReducer } from './editor/reducers'
|
||||||
import { EditorConfig } from './editor/types'
|
import { EditorConfig } from './editor/types'
|
||||||
import { FrontendConfigReducer } from './frontend-config/reducers'
|
import { FrontendConfigReducer } from './frontend-config/reducers'
|
||||||
|
@ -11,6 +13,7 @@ import { MaybeUserState } from './user/types'
|
||||||
export interface ApplicationState {
|
export interface ApplicationState {
|
||||||
user: MaybeUserState;
|
user: MaybeUserState;
|
||||||
backendConfig: BackendConfig;
|
backendConfig: BackendConfig;
|
||||||
|
banner: BannerState;
|
||||||
frontendConfig: FrontendConfig;
|
frontendConfig: FrontendConfig;
|
||||||
editorConfig: EditorConfig;
|
editorConfig: EditorConfig;
|
||||||
}
|
}
|
||||||
|
@ -18,6 +21,7 @@ export interface ApplicationState {
|
||||||
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
export const allReducers: Reducer<ApplicationState> = combineReducers<ApplicationState>({
|
||||||
user: UserReducer,
|
user: UserReducer,
|
||||||
backendConfig: BackendConfigReducer,
|
backendConfig: BackendConfigReducer,
|
||||||
|
banner: BannerReducer,
|
||||||
frontendConfig: FrontendConfigReducer,
|
frontendConfig: FrontendConfigReducer,
|
||||||
editorConfig: EditorConfigReducer
|
editorConfig: EditorConfigReducer
|
||||||
})
|
})
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue