From 8a5f86a89ee187295e093a72cad5de2f048634c6 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Sat, 1 Oct 2022 18:06:31 +0200 Subject: [PATCH] fix(motd): move fetch into component Signed-off-by: Tilman Vatteroth --- cypress/e2e/motd.spec.ts | 89 ++---------- .../application-loader/initializers/index.ts | 5 - .../__snapshots__/motd-modal.test.tsx.snap | 88 ++++++++++++ .../common/motd-modal/fetch-motd.test.ts | 130 ++++++++++++++++++ .../motd-modal}/fetch-motd.ts | 17 ++- .../common/motd-modal/motd-modal.test.tsx | 73 ++++++++++ .../common/motd-modal/motd-modal.tsx | 65 +++++---- .../common/motd-modal/motd-renderer.tsx | 17 ++- src/redux/application-state.d.ts | 2 - src/redux/motd/methods.ts | 32 ----- src/redux/motd/reducers.ts | 61 -------- src/redux/motd/types.ts | 32 ----- src/redux/reducers.ts | 2 - 13 files changed, 359 insertions(+), 254 deletions(-) create mode 100644 src/components/common/motd-modal/__snapshots__/motd-modal.test.tsx.snap create mode 100644 src/components/common/motd-modal/fetch-motd.test.ts rename src/components/{application-loader/initializers => common/motd-modal}/fetch-motd.ts (83%) create mode 100644 src/components/common/motd-modal/motd-modal.test.tsx delete mode 100644 src/redux/motd/methods.ts delete mode 100644 src/redux/motd/reducers.ts delete mode 100644 src/redux/motd/types.ts diff --git a/cypress/e2e/motd.spec.ts b/cypress/e2e/motd.spec.ts index 085491c27..3799ab9cb 100644 --- a/cypress/e2e/motd.spec.ts +++ b/cypress/e2e/motd.spec.ts @@ -10,101 +10,28 @@ const motdMockContent = 'This is the **mock** Motd call' const motdMockHtml = 'This is the mock Motd call' describe('Motd', () => { - const mockExistingMotd = (useEtag?: boolean, content = motdMockContent) => { + it("shows, dismisses and won't show again a motd modal", () => { + localStorage.removeItem(MOTD_LOCAL_STORAGE_KEY) cy.intercept('GET', 'public/motd.md', { statusCode: 200, - headers: { [useEtag ? 'etag' : 'Last-Modified']: MOCK_LAST_MODIFIED }, - body: content + headers: { 'Last-Modified': MOCK_LAST_MODIFIED }, + body: motdMockContent }) cy.intercept('HEAD', 'public/motd.md', { statusCode: 200, - headers: { [useEtag ? 'etag' : 'Last-Modified']: MOCK_LAST_MODIFIED } + headers: { 'Last-Modified': MOCK_LAST_MODIFIED } }) - } - - beforeEach(() => { - localStorage.removeItem(MOTD_LOCAL_STORAGE_KEY) - }) - - it('shows the correct alert Motd text', () => { - mockExistingMotd() cy.visitHome() - cy.getByCypressId('motd').find('.markdown-body').should('contain.html', motdMockHtml) - }) - - it("doesn't allow html in the motd", () => { - mockExistingMotd(false, '') - cy.visitHome() - cy.getByCypressId('motd').find('.markdown-body').should('have.html', '

<iframe></iframe>

\n') - }) - - it('can be dismissed using etag', () => { - mockExistingMotd(true) - cy.visitHome() - cy.getByCypressId('motd').find('.markdown-body').should('contain.html', motdMockHtml) + cy.getByCypressId('motd-modal').find('.markdown-body').should('contain.html', motdMockHtml) cy.getByCypressId('motd-dismiss') .click() .then(() => { expect(localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)).to.equal(MOCK_LAST_MODIFIED) }) - cy.getByCypressId('motd').should('not.exist') - }) - - it('can be dismissed', () => { - mockExistingMotd() - cy.visitHome() - cy.getByCypressId('motd').find('.markdown-body').should('contain.html', motdMockHtml) - cy.getByCypressId('motd-dismiss') - .click() - .then(() => { - expect(localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)).to.equal(MOCK_LAST_MODIFIED) - }) - cy.getByCypressId('motd').should('not.exist') - }) - - it("won't show again after dismiss and reload", () => { - mockExistingMotd() - cy.visitHome() - cy.getByCypressId('motd').find('.markdown-body').should('contain.html', motdMockHtml) - cy.getByCypressId('motd-dismiss') - .click() - .then(() => { - expect(localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)).to.equal(MOCK_LAST_MODIFIED) - }) - cy.getByCypressId('motd').should('not.exist') + cy.getByCypressId('motd-modal').should('not.exist') cy.reload() cy.get('main').should('exist') - cy.getByCypressId('motd').should('not.exist') - }) - - it('will show again after reload without dismiss', () => { - mockExistingMotd() - cy.visitHome() - cy.getByCypressId('motd').find('.markdown-body').should('contain.html', motdMockHtml) - cy.reload() - cy.get('main').should('exist') - cy.getByCypressId('motd').find('.markdown-body').should('contain.html', motdMockHtml) - }) - - it("won't show again after dismiss and page navigation", () => { - mockExistingMotd() - cy.visitHome() - cy.getByCypressId('motd').find('.markdown-body').should('contain.html', motdMockHtml) - cy.getByCypressId('motd-dismiss') - .click() - .then(() => { - expect(localStorage.getItem(MOTD_LOCAL_STORAGE_KEY)).to.equal(MOCK_LAST_MODIFIED) - }) - cy.getByCypressId('motd').should('not.exist') - cy.getByCypressId('navLinkHistory').click() - cy.get('main').should('exist') - cy.getByCypressId('motd').should('not.exist') - }) - - it("won't show if no file exists", () => { - cy.visitHome() - cy.get('main').should('exist') - cy.getByCypressId('motd').should('not.exist') + cy.getByCypressId('motd-modal').should('not.exist') }) }) diff --git a/src/components/application-loader/initializers/index.ts b/src/components/application-loader/initializers/index.ts index 6df08575d..b0331fccc 100644 --- a/src/components/application-loader/initializers/index.ts +++ b/src/components/application-loader/initializers/index.ts @@ -6,7 +6,6 @@ import { setUpI18n } from './setupI18n' import { refreshHistoryState } from '../../../redux/history/methods' -import { fetchMotd } from './fetch-motd' import { fetchAndSetUser } from '../../login-page/auth/utils' import { fetchFrontendConfig } from './fetch-frontend-config' import { loadDarkMode } from './load-dark-mode' @@ -65,10 +64,6 @@ export const createSetUpTaskList = (): InitTask[] => { name: 'Fetch user information', task: fetchUserInformation }, - { - name: 'Motd', - task: fetchMotd - }, { name: 'Load history state', task: refreshHistoryState diff --git a/src/components/common/motd-modal/__snapshots__/motd-modal.test.tsx.snap b/src/components/common/motd-modal/__snapshots__/motd-modal.test.tsx.snap new file mode 100644 index 000000000..d339358b7 --- /dev/null +++ b/src/components/common/motd-modal/__snapshots__/motd-modal.test.tsx.snap @@ -0,0 +1,88 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`motd modal doesn't allow html in the motd 1`] = ` +
+ + This is a mock implementation of a Modal: + + + + + +
+`; + +exports[`motd modal doesn't render a modal if no motd has been fetched 1`] = ` +
+ +
+`; + +exports[`motd modal renders a modal if a motd was fetched and can dismiss it 1`] = ` +
+ + This is a mock implementation of a Modal: + + + + + +
+`; + +exports[`motd modal renders a modal if a motd was fetched and can dismiss it 2`] = ` +
+ + This is a mock implementation of a Modal: + Modal is invisible + +
+`; diff --git a/src/components/common/motd-modal/fetch-motd.test.ts b/src/components/common/motd-modal/fetch-motd.test.ts new file mode 100644 index 000000000..1227125fe --- /dev/null +++ b/src/components/common/motd-modal/fetch-motd.test.ts @@ -0,0 +1,130 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Mock } from 'ts-mockery' +import { fetchMotd } from './fetch-motd' + +describe('fetch motd', () => { + const motdUrl = 'public/motd.md' + + beforeEach(() => { + window.localStorage.clear() + }) + afterEach(() => { + jest.resetAllMocks() + jest.resetModules() + }) + beforeAll(() => { + global.fetch = jest.fn() + }) + + const mockFetch = ( + responseText: string, + lastModified: string | null, + etag?: string | null + ): jest.SpyInstance> => { + return jest.spyOn(global, 'fetch').mockImplementation((url: RequestInfo | URL) => { + if (url !== motdUrl) { + return Promise.reject('wrong url') + } + return Promise.resolve( + Mock.of({ + headers: Mock.of({ + get: (name: string) => { + return name === 'Last-Modified' ? lastModified : name === 'etag' ? etag ?? null : null + } + }), + text: () => Promise.resolve(responseText), + status: 200 + }) + ) + }) + } + + const mockFileNotFoundFetch = () => { + jest.spyOn(global, 'fetch').mockImplementation(() => + Promise.resolve( + Mock.of({ + status: 500 + }) + ) + ) + } + + describe('date detection', () => { + it('will return the last-modified value if available', async () => { + mockFetch('mocked motd', 'yesterday-modified', null) + const result = fetchMotd() + await expect(result).resolves.toStrictEqual({ + motdText: 'mocked motd', + lastModified: 'yesterday-modified' + }) + }) + it('will return the etag if last-modified is not returned', async () => { + mockFetch('mocked motd', null, 'yesterday-etag') + const result = fetchMotd() + await expect(result).resolves.toStrictEqual({ + motdText: 'mocked motd', + lastModified: 'yesterday-etag' + }) + }) + it('will prefer the last-modified header over the etag', async () => { + mockFetch('mocked motd', 'yesterday-last', 'yesterday-etag') + const result = fetchMotd() + await expect(result).resolves.toStrictEqual({ + motdText: 'mocked motd', + lastModified: 'yesterday-last' + }) + }) + it('will return an empty value if neither the last-modified value nor the etag is returned', async () => { + mockFetch('mocked motd', null, null) + const result = fetchMotd() + await expect(result).resolves.toStrictEqual({ + motdText: 'mocked motd', + lastModified: null + }) + }) + }) + + it('can fetch a motd if no last modified value has been memorized', async () => { + mockFetch('mocked motd', 'yesterday') + const result = fetchMotd() + await expect(result).resolves.toStrictEqual({ + motdText: 'mocked motd', + lastModified: 'yesterday' + }) + }) + + it("can detect that the motd hasn't been updated", async () => { + mockFetch('mocked motd', 'yesterday') + window.localStorage.setItem('motd.lastModified', 'yesterday') + const result = fetchMotd() + await expect(result).resolves.toStrictEqual(undefined) + }) + + it('can detect that the motd has been updated', async () => { + mockFetch('mocked motd', 'yesterday') + window.localStorage.setItem('motd.lastModified', 'the day before yesterday') + const result = fetchMotd() + await expect(result).resolves.toStrictEqual({ + motdText: 'mocked motd', + lastModified: 'yesterday' + }) + }) + + it("won't fetch a motd if no file was found", async () => { + mockFileNotFoundFetch() + const result = fetchMotd() + await expect(result).resolves.toStrictEqual(undefined) + }) + + it("won't fetch a motd update if no file was found", async () => { + mockFileNotFoundFetch() + window.localStorage.setItem('motd.lastModified', 'the day before yesterday') + const result = fetchMotd() + await expect(result).resolves.toStrictEqual(undefined) + }) +}) diff --git a/src/components/application-loader/initializers/fetch-motd.ts b/src/components/common/motd-modal/fetch-motd.ts similarity index 83% rename from src/components/application-loader/initializers/fetch-motd.ts rename to src/components/common/motd-modal/fetch-motd.ts index c43594595..b2be7f4d7 100644 --- a/src/components/application-loader/initializers/fetch-motd.ts +++ b/src/components/common/motd-modal/fetch-motd.ts @@ -4,13 +4,17 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { setMotd } from '../../../redux/motd/methods' import { Logger } from '../../../utils/logger' import { defaultConfig } from '../../../api/common/default-config' export const MOTD_LOCAL_STORAGE_KEY = 'motd.lastModified' const log = new Logger('Motd') +export interface MotdApiResponse { + motdText: string + lastModified: string | null +} + /** * 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. @@ -18,7 +22,7 @@ const log = new Logger('Motd') * will be compared to the saved value from the browser's local storage. * @return A promise that gets resolved if the motd was fetched successfully. */ -export const fetchMotd = async (): Promise => { +export const fetchMotd = async (): Promise => { const cachedLastModified = window.localStorage.getItem(MOTD_LOCAL_STORAGE_KEY) const motdUrl = `public/motd.md` @@ -28,11 +32,11 @@ export const fetchMotd = async (): Promise => { method: 'HEAD' }) if (response.status !== 200) { - return + return undefined } const lastModified = response.headers.get('Last-Modified') || response.headers.get('etag') if (lastModified === cachedLastModified) { - return + return undefined } } @@ -41,7 +45,7 @@ export const fetchMotd = async (): Promise => { }) if (response.status !== 200) { - return + return undefined } const lastModified = response.headers.get('Last-Modified') || response.headers.get('etag') @@ -49,6 +53,5 @@ export const fetchMotd = async (): Promise => { log.warn("'Last-Modified' or 'Etag' not found for motd.md!") } - const motdText = await response.text() - setMotd(motdText, lastModified) + return { motdText: await response.text(), lastModified } } diff --git a/src/components/common/motd-modal/motd-modal.test.tsx b/src/components/common/motd-modal/motd-modal.test.tsx new file mode 100644 index 000000000..9c18fcfdb --- /dev/null +++ b/src/components/common/motd-modal/motd-modal.test.tsx @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { MotdModal } from './motd-modal' +import { act, render, screen } from '@testing-library/react' +import * as fetchMotdModule from './fetch-motd' +import * as CommonModalModule from '../modals/common-modal' +import type { PropsWithChildren } from 'react' +import React from 'react' +import type { CommonModalProps } from '../modals/common-modal' +import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n' + +jest.mock('./fetch-motd') +jest.mock('../modals/common-modal') + +describe('motd modal', () => { + beforeAll(mockI18n) + + afterAll(() => { + jest.resetAllMocks() + jest.resetModules() + }) + + beforeAll(() => { + jest.spyOn(CommonModalModule, 'CommonModal').mockImplementation((({ children, show }) => { + return ( + + This is a mock implementation of a Modal: + {show ? {children} : 'Modal is invisible'} + + ) + }) as React.FC>) + }) + + it('renders a modal if a motd was fetched and can dismiss it', async () => { + jest.spyOn(fetchMotdModule, 'fetchMotd').mockImplementation(() => { + return Promise.resolve({ + motdText: 'very important mock text!', + lastModified: 'yesterday' + }) + }) + const view = render() + await screen.findByTestId('motd-renderer') + expect(view.container).toMatchSnapshot() + + const button = await screen.findByTestId('motd-dismiss') + act(() => { + button.click() + }) + expect(view.container).toMatchSnapshot() + }) + + it("doesn't render a modal if no motd has been fetched", async () => { + jest.spyOn(fetchMotdModule, 'fetchMotd').mockImplementation(() => { + return Promise.resolve(undefined) + }) + const view = render() + await screen.findByTestId('loaded not visible') + expect(view.container).toMatchSnapshot() + }) + + it("doesn't allow html in the motd", async () => { + jest.spyOn(fetchMotdModule, 'fetchMotd').mockImplementation(() => { + return Promise.resolve({ motdText: '', lastModified: 'yesterday' }) + }) + const view = render() + await screen.findByTestId('motd-renderer') + expect(view.container).toMatchSnapshot() + }) +}) diff --git a/src/components/common/motd-modal/motd-modal.tsx b/src/components/common/motd-modal/motd-modal.tsx index 288a1ac88..287b17d10 100644 --- a/src/components/common/motd-modal/motd-modal.tsx +++ b/src/components/common/motd-modal/motd-modal.tsx @@ -1,19 +1,22 @@ /* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) * * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Suspense, useCallback } from 'react' +import React, { Suspense, useCallback, useEffect, useState } 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' -import { cypressId } from '../../../utils/cypress-attribute' import { WaitSpinner } from '../wait-spinner/wait-spinner' +import { fetchMotd, MOTD_LOCAL_STORAGE_KEY } from './fetch-motd' +import { useAsync } from 'react-use' +import { Logger } from '../../../utils/logger' +import { testId } from '../../../utils/test-id' +import { cypressId } from '../../../utils/cypress-attribute' const MotdRenderer = React.lazy(() => import('./motd-renderer')) +const logger = new Logger('Motd') /** * Reads the motd from the global application state and shows it in a modal. @@ -22,31 +25,39 @@ const MotdRenderer = React.lazy(() => import('./motd-renderer')) */ export const MotdModal: React.FC = () => { useTranslation() - const motdState = useApplicationState((state) => state.motd) + + const { error, loading, value } = useAsync(fetchMotd) + const [dismissed, setDismissed] = useState(false) const dismiss = useCallback(() => { - if (!motdState) { - return + if (value?.lastModified) { + window.localStorage.setItem(MOTD_LOCAL_STORAGE_KEY, value.lastModified) } - dismissMotd() - }, [motdState]) + setDismissed(true) + }, [value]) - if (motdState === null || motdState.dismissed) { - return null - } else { - return ( - - - }> - - - - - - - - ) + useEffect(() => { + if (error) { + logger.error('Error while fetching motd', error) + } + }, [error]) + + if (process.env.NODE_ENV === 'test' && !loading && !value) { + return } + + return ( + + + }> + + + + + + + + ) } diff --git a/src/components/common/motd-modal/motd-renderer.tsx b/src/components/common/motd-modal/motd-renderer.tsx index 7eb041138..c1d8459c8 100644 --- a/src/components/common/motd-modal/motd-renderer.tsx +++ b/src/components/common/motd-modal/motd-renderer.tsx @@ -10,18 +10,22 @@ import { useConvertMarkdownToReactDom } from '../../markdown-renderer/hooks/use- 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 { useApplicationState } from '../../../hooks/common/use-application-state' 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 = () => { +export const MotdRenderer: React.FC = ({ content }) => { const extensions = useMemo( () => [ new YoutubeMarkdownExtension(), @@ -38,11 +42,14 @@ export const MotdRenderer: React.FC = () => { [] ) - const motdState = useApplicationState((state) => state.motd) - const lines = useMemo(() => (motdState ? motdState.text.split('\n') : []), [motdState]) + const lines = useMemo(() => content.split('\n'), [content]) const dom = useConvertMarkdownToReactDom(lines, extensions, true, false) - return
{dom}
+ return ( +
+ {dom} +
+ ) } export default MotdRenderer diff --git a/src/redux/application-state.d.ts b/src/redux/application-state.d.ts index abf64cffd..50d455f17 100644 --- a/src/redux/application-state.d.ts +++ b/src/redux/application-state.d.ts @@ -6,7 +6,6 @@ import type { OptionalUserState } from './user/types' import type { Config } from '../api/config/types' -import type { OptionalMotdState } from './motd/types' import type { EditorConfig } from './editor/types' import type { DarkModeConfig } from './dark-mode/types' import type { NoteDetails } from './note-details/types/note-details' @@ -17,7 +16,6 @@ import type { RealtimeState } from './realtime/types' export interface ApplicationState { user: OptionalUserState config: Config - motd: OptionalMotdState history: HistoryEntryWithOrigin[] editorConfig: EditorConfig darkMode: DarkModeConfig diff --git a/src/redux/motd/methods.ts b/src/redux/motd/methods.ts deleted file mode 100644 index f89fe8e12..000000000 --- a/src/redux/motd/methods.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { store } from '..' -import type { DismissMotdAction, SetMotdAction } from './types' -import { MotdActionType } from './types' - -/** - * Sets a not-dismissed motd message in the global application state. - * - * @param text The motd text content - * @param lastModified An identifier that describes when the motd was changed the last time. - */ -export const setMotd = (text: string, lastModified: string | null): void => { - store.dispatch({ - type: MotdActionType.SET_MOTD, - text, - lastModified - } as SetMotdAction) -} - -/** - * Dismisses the currently saved motd message. - */ -export const dismissMotd = (): void => { - store.dispatch({ - type: MotdActionType.DISMISS_MOTD - } as DismissMotdAction) -} diff --git a/src/redux/motd/reducers.ts b/src/redux/motd/reducers.ts deleted file mode 100644 index dc7cecd9a..000000000 --- a/src/redux/motd/reducers.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { Reducer } from 'redux' -import type { MotdActions, MotdState, OptionalMotdState } from './types' -import { MotdActionType } from './types' -import { MOTD_LOCAL_STORAGE_KEY } from '../../components/application-loader/initializers/fetch-motd' - -/** - * A reducer that modifies the {@link OptionalMotdState motd state} in the global application state. - */ -export const MotdReducer: Reducer = ( - state: OptionalMotdState = null, - action: MotdActions -) => { - switch (action.type) { - case MotdActionType.SET_MOTD: - return createNewMotdState(action.text, action.lastModified) - case MotdActionType.DISMISS_MOTD: - return createDismissedMotdState(state) - default: - return state - } -} - -/** - * Creates a new {@link MotdState motd state} by copying the old state and setting the dismissed flag. - * It also writes the "last-modified" identifier into the browser's local storage. - * - * @param oldState The current motd state that should be copied - * @return The new state - */ -const createDismissedMotdState = (oldState: OptionalMotdState): OptionalMotdState => { - if (oldState === null) { - return null - } else { - if (oldState.lastModified) { - window.localStorage.setItem(MOTD_LOCAL_STORAGE_KEY, oldState.lastModified) - } - return { - ...oldState, - dismissed: true - } - } -} - -/** - * Creates a new not-dismissed motd state. - * @param text The motd text - * @param lastModified An identifier that describes when the motd text was changed the last time - */ -const createNewMotdState = (text: string, lastModified: string | null): MotdState => { - return { - text, - lastModified, - dismissed: false - } -} diff --git a/src/redux/motd/types.ts b/src/redux/motd/types.ts deleted file mode 100644 index 845952be7..000000000 --- a/src/redux/motd/types.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { Action } from 'redux' - -export enum MotdActionType { - SET_MOTD = 'motd/set', - DISMISS_MOTD = 'motd/dismiss' -} - -export type MotdActions = SetMotdAction | DismissMotdAction - -export interface SetMotdAction extends Action { - type: MotdActionType.SET_MOTD - text: string - lastModified: string | null -} - -export interface DismissMotdAction extends Action { - type: MotdActionType.DISMISS_MOTD -} - -export interface MotdState { - text: string - lastModified: string | null - dismissed: boolean -} - -export type OptionalMotdState = MotdState | null diff --git a/src/redux/reducers.ts b/src/redux/reducers.ts index 6f5368349..13581bf19 100644 --- a/src/redux/reducers.ts +++ b/src/redux/reducers.ts @@ -8,7 +8,6 @@ import type { Reducer } from 'redux' import { combineReducers } from 'redux' import { UserReducer } from './user/reducers' import { ConfigReducer } from './config/reducers' -import { MotdReducer } from './motd/reducers' import { HistoryReducer } from './history/reducers' import { EditorConfigReducer } from './editor/reducers' import { DarkModeConfigReducer } from './dark-mode/reducers' @@ -20,7 +19,6 @@ import { RealtimeReducer } from './realtime/reducers' export const allReducers: Reducer = combineReducers({ user: UserReducer, config: ConfigReducer, - motd: MotdReducer, history: HistoryReducer, editorConfig: EditorConfigReducer, darkMode: DarkModeConfigReducer,