diff --git a/public/api/v2/config b/public/api/v2/config index 61dff0024..a0ec2dcb8 100644 --- a/public/api/v2/config +++ b/public/api/v2/config @@ -13,6 +13,10 @@ "email": true, "openid": true }, + "banner": { + "text": "This is the test banner text", + "timestamp": "2020-05-22T20:46:08.962Z" + }, "customAuthNames": { "ldap": "FooBar", "oauth2": "Olaf2", diff --git a/public/api/v2/notes/banner b/public/api/v2/notes/banner new file mode 100644 index 000000000..9ec51b790 --- /dev/null +++ b/public/api/v2/notes/banner @@ -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 +} diff --git a/public/api/v2/notes/old b/public/api/v2/notes/old index edfd3512e..07e07dc88 100644 --- a/public/api/v2/notes/old +++ b/public/api/v2/notes/old @@ -10,5 +10,5 @@ "createtime": "2020-05-22T20:46:08.962Z", "content": "test123", "authorship": [], - "preVersionTwoNote": true + "preVersionTwoNote": false } diff --git a/src/api/backend-config/types.ts b/src/api/backend-config/types.ts index 989b3558a..874861f79 100644 --- a/src/api/backend-config/types.ts +++ b/src/api/backend-config/types.ts @@ -1,11 +1,17 @@ export interface BackendConfig { allowAnonymous: boolean, authProviders: AuthProvidersState, + banner: BannerConfig, customAuthNames: CustomAuthNames, specialLinks: SpecialLinks, version: BackendVersion, } +export interface BannerConfig { + text: string + timestamp: string +} + export interface BackendVersion { version: string, sourceCodeUrl: string diff --git a/src/components/application-loader/initializers/configLoader.ts b/src/components/application-loader/initializers/configLoader.ts index e96f026fc..13cf0ef7d 100644 --- a/src/components/application-loader/initializers/configLoader.ts +++ b/src/components/application-loader/initializers/configLoader.ts @@ -1,6 +1,7 @@ import { getBackendConfig } from '../../../api/backend-config' import { getFrontendConfig } from '../../../api/frontend-config' import { setBackendConfig } from '../../../redux/backend-config/methods' +import { setBanner } from '../../../redux/banner/methods' import { setFrontendConfig } from '../../../redux/frontend-config/methods' import { getAndSetUser } from '../../../utils/apiUtils' @@ -17,5 +18,14 @@ export const loadAllConfig: (baseUrl: string) => Promise = async (baseUrl) } 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() } diff --git a/src/components/editor/editor.tsx b/src/components/editor/editor.tsx index a5b50ee05..4e3fa0fcc 100644 --- a/src/components/editor/editor.tsx +++ b/src/components/editor/editor.tsx @@ -1,18 +1,15 @@ -import React, { useEffect, useState } from 'react' +import React, { Fragment, useEffect, useState } from 'react' import { useSelector } from 'react-redux' import useMedia from 'use-media' import { ApplicationState } from '../../redux' import { setEditorModeConfig } from '../../redux/editor/methods' import { Splitter } from '../common/splitter/splitter' +import { InfoBanner } from '../landing/layout/info-banner' import { EditorWindow } from './editor-window/editor-window' import { MarkdownPreview } from './markdown-preview/markdown-preview' import { EditorMode } from './task-bar/editor-view-mode' import { TaskBar } from './task-bar/task-bar' -interface RouteParameters { - id: string -} - const Editor: React.FC = () => { const editorMode: EditorMode = useSelector((state: ApplicationState) => state.editorConfig.editorMode) const isWide = useMedia({ minWidth: 576 }) @@ -29,15 +26,18 @@ const Editor: React.FC = () => { }, [editorMode, firstDraw, isWide]) return ( -
- - } - showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)} - right={} - containerClassName={'overflow-hidden'}/> -
+ + +
+ + } + showRight={editorMode === EditorMode.PREVIEW || (editorMode === EditorMode.BOTH)} + right={} + containerClassName={'overflow-hidden'}/> +
+
) } diff --git a/src/components/landing/landing-layout.tsx b/src/components/landing/landing-layout.tsx index 421fda9eb..693e19eb1 100644 --- a/src/components/landing/landing-layout.tsx +++ b/src/components/landing/landing-layout.tsx @@ -1,11 +1,13 @@ import React from 'react' import { Container } from 'react-bootstrap' import { Footer } from './layout/footer/footer' +import { InfoBanner } from './layout/info-banner' import { HeaderBar } from './layout/navigation/header-bar/header-bar' export const LandingLayout: React.FC = ({ children }) => { return ( +
diff --git a/src/components/landing/layout/info-banner.tsx b/src/components/landing/layout/info-banner.tsx new file mode 100644 index 000000000..00d167eb8 --- /dev/null +++ b/src/components/landing/layout/info-banner.tsx @@ -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 ( + + + + {bannerState.text} + + + + + ) +} diff --git a/src/redux/backend-config/reducers.ts b/src/redux/backend-config/reducers.ts index 98e50a1e7..e4c642ae9 100644 --- a/src/redux/backend-config/reducers.ts +++ b/src/redux/backend-config/reducers.ts @@ -17,6 +17,10 @@ export const initialState: BackendConfig = { email: false, openid: false }, + banner: { + text: '', + timestamp: '' + }, customAuthNames: { ldap: '', oauth2: '', diff --git a/src/redux/banner/methods.ts b/src/redux/banner/methods.ts new file mode 100644 index 000000000..0ebf7bd1b --- /dev/null +++ b/src/redux/banner/methods.ts @@ -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) +} diff --git a/src/redux/banner/reducers.ts b/src/redux/banner/reducers.ts new file mode 100644 index 000000000..fb2dce761 --- /dev/null +++ b/src/redux/banner/reducers.ts @@ -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 = (state: BannerState = initialState, action: BannerActions) => { + switch (action.type) { + case BannerActionType.SET_BANNER: + return (action as SetBannerAction).state + default: + return state + } +} diff --git a/src/redux/banner/types.ts b/src/redux/banner/types.ts new file mode 100644 index 000000000..cc3c31088 --- /dev/null +++ b/src/redux/banner/types.ts @@ -0,0 +1,19 @@ +import { Action } from 'redux' + +export enum BannerActionType { + SET_BANNER = 'banner/set', +} + +export interface BannerActions extends Action { + type: BannerActionType; +} + +export interface SetBannerAction extends BannerActions { + state: BannerState; +} + +export interface BannerState { + show: boolean + text: string + timestamp: string +} diff --git a/src/redux/index.ts b/src/redux/index.ts index 58543d0b5..b6c7ce2e6 100644 --- a/src/redux/index.ts +++ b/src/redux/index.ts @@ -2,6 +2,8 @@ import { combineReducers, Reducer } from 'redux' import { BackendConfig } from '../api/backend-config/types' import { FrontendConfig } from '../api/frontend-config/types' import { BackendConfigReducer } from './backend-config/reducers' +import { BannerReducer } from './banner/reducers' +import { BannerState } from './banner/types' import { EditorConfigReducer } from './editor/reducers' import { EditorConfig } from './editor/types' import { FrontendConfigReducer } from './frontend-config/reducers' @@ -11,6 +13,7 @@ import { MaybeUserState } from './user/types' export interface ApplicationState { user: MaybeUserState; backendConfig: BackendConfig; + banner: BannerState; frontendConfig: FrontendConfig; editorConfig: EditorConfig; } @@ -18,6 +21,7 @@ export interface ApplicationState { export const allReducers: Reducer = combineReducers({ user: UserReducer, backendConfig: BackendConfigReducer, + banner: BannerReducer, frontendConfig: FrontendConfigReducer, editorConfig: EditorConfigReducer })