From 880e542351ecca3afb9f14f375cfd316814b6352 Mon Sep 17 00:00:00 2001 From: Tilman Vatteroth Date: Wed, 11 May 2022 12:47:58 +0200 Subject: [PATCH] Add note loading boundary (#2040) * Remove redundant equal value Signed-off-by: Tilman Vatteroth * Add NoteLoadingBoundary to fetch note from API before rendering Signed-off-by: Tilman Vatteroth * Improve debug message for setHandler Signed-off-by: Tilman Vatteroth * Add test for boundary Signed-off-by: Tilman Vatteroth * Use common error page for note loading errors Signed-off-by: Tilman Vatteroth * Fix tests Signed-off-by: Tilman Vatteroth * Format code Signed-off-by: Tilman Vatteroth * Add missing snapshot Signed-off-by: Tilman Vatteroth * Reformat code Signed-off-by: Tilman Vatteroth --- locales/en.json | 9 ++ src/api/notes/index.ts | 4 +- .../application-loader-error.ts | 2 +- .../application-loader/application-loader.tsx | 18 ++- .../loading-screen/loading-screen.tsx | 15 +-- .../note-loading-boundary.test.tsx.snap | 32 +++++ .../hooks/use-load-note-from-server.ts | 28 +++++ .../note-loading-boundary.test.tsx | 115 ++++++++++++++++++ .../note-loading-boundary.tsx | 35 ++++++ .../ErrorWhileLoadingNoteAlert.tsx | 27 ---- .../LoadingNoteAlert.tsx | 21 ---- .../editor-page/editor-page-content.tsx | 36 ++---- .../hooks/useLoadNoteFromServer.ts | 36 ------ .../hooks/useUpdateLocalHistoryEntry.ts | 6 +- .../window-post-message-communicator.ts | 2 +- ...te-markdown-content-without-frontmatter.ts | 12 +- src/pages/n/{[id].tsx => [noteId].tsx} | 9 +- src/pages/p/{[id].tsx => [noteId].tsx} | 15 +-- src/pages/s/{[id].tsx => [noteId].tsx} | 26 ++-- 19 files changed, 282 insertions(+), 166 deletions(-) create mode 100644 src/components/common/note-loading-boundary/__snapshots__/note-loading-boundary.test.tsx.snap create mode 100644 src/components/common/note-loading-boundary/hooks/use-load-note-from-server.ts create mode 100644 src/components/common/note-loading-boundary/note-loading-boundary.test.tsx create mode 100644 src/components/common/note-loading-boundary/note-loading-boundary.tsx delete mode 100644 src/components/document-read-only-page/ErrorWhileLoadingNoteAlert.tsx delete mode 100644 src/components/document-read-only-page/LoadingNoteAlert.tsx delete mode 100644 src/components/editor-page/hooks/useLoadNoteFromServer.ts rename src/pages/n/{[id].tsx => [noteId].tsx} (68%) rename src/pages/p/{[id].tsx => [noteId].tsx} (64%) rename src/pages/s/{[id].tsx => [noteId].tsx} (59%) diff --git a/locales/en.json b/locales/en.json index ce3a3daf1..27067d703 100644 --- a/locales/en.json +++ b/locales/en.json @@ -40,6 +40,15 @@ "uploadMessage": "Uploading file" } }, + "noteLoadingBoundary": { + "errorWhileLoadingContent": "Error while loading note." + }, + "api": { + "note": { + "notFound": "Note not found.", + "accessDenied": "You don't have the needed permission to access this note." + } + }, "landing": { "intro": { "exploreFeatures": "Explore all features", diff --git a/src/api/notes/index.ts b/src/api/notes/index.ts index 7791a8bd3..a91c28280 100644 --- a/src/api/notes/index.ts +++ b/src/api/notes/index.ts @@ -15,7 +15,9 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap * @return Content and metadata of the specified note. */ export const getNote = async (noteIdOrAlias: string): Promise => { - const response = await new GetApiRequestBuilder('notes/' + noteIdOrAlias).sendRequest() + const response = await new GetApiRequestBuilder('notes/' + noteIdOrAlias) + .withStatusCodeErrorMapping({ 404: 'api.note.notFound', 403: 'api.note.accessDenied' }) + .sendRequest() return response.asParsedJsonObject() } diff --git a/src/components/application-loader/application-loader-error.ts b/src/components/application-loader/application-loader-error.ts index e415f3342..b2efc1a2b 100644 --- a/src/components/application-loader/application-loader-error.ts +++ b/src/components/application-loader/application-loader-error.ts @@ -5,6 +5,6 @@ */ export class ApplicationLoaderError extends Error { constructor(taskName: string) { - super(`Task ${taskName} failed`) + super(`The task ${taskName} failed`) } } diff --git a/src/components/application-loader/application-loader.tsx b/src/components/application-loader/application-loader.tsx index 0075a688e..f76109756 100644 --- a/src/components/application-loader/application-loader.tsx +++ b/src/components/application-loader/application-loader.tsx @@ -5,7 +5,7 @@ */ import type { PropsWithChildren } from 'react' -import React, { Suspense } from 'react' +import React, { Fragment, Suspense, useMemo } from 'react' import { createSetUpTaskList } from './initializers' import { LoadingScreen } from './loading-screen/loading-screen' import { Logger } from '../../utils/logger' @@ -27,8 +27,20 @@ export const ApplicationLoader: React.FC> = ({ childr } }, []) - if (loading) { - return + const errorBlock = useMemo(() => { + if (error) { + return ( + + {error.message} +
+ For further information look into the browser console. +
+ ) + } + }, [error]) + + if (loading || !!errorBlock) { + return } else { return }>{children} } diff --git a/src/components/application-loader/loading-screen/loading-screen.tsx b/src/components/application-loader/loading-screen/loading-screen.tsx index 6cb7667f7..3dafb3966 100644 --- a/src/components/application-loader/loading-screen/loading-screen.tsx +++ b/src/components/application-loader/loading-screen/loading-screen.tsx @@ -4,6 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import type { ReactElement } from 'react' import React from 'react' import { Alert } from 'react-bootstrap' import { LoadingAnimation } from './loading-animation' @@ -11,7 +12,7 @@ import { ShowIf } from '../../common/show-if/show-if' import styles from '../application-loader.module.scss' export interface LoadingScreenProps { - failedTaskName?: string + errorMessage?: string | ReactElement } /** @@ -19,20 +20,16 @@ export interface LoadingScreenProps { * * @param failedTaskName Should be set if a task failed to load. The name will be shown on screen. */ -export const LoadingScreen: React.FC = ({ failedTaskName }) => { +export const LoadingScreen: React.FC = ({ errorMessage }) => { return (
- +
- - - The task {failedTaskName} failed. -
- For further information look into the browser console. -
+ + {errorMessage}
) diff --git a/src/components/common/note-loading-boundary/__snapshots__/note-loading-boundary.test.tsx.snap b/src/components/common/note-loading-boundary/__snapshots__/note-loading-boundary.test.tsx.snap new file mode 100644 index 000000000..e1754774c --- /dev/null +++ b/src/components/common/note-loading-boundary/__snapshots__/note-loading-boundary.test.tsx.snap @@ -0,0 +1,32 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Note loading boundary loads a note 1`] = ` +
+ + success! + +
+`; + +exports[`Note loading boundary shows an error 1`] = ` +
+ + This is a mock for CommonErrorPage. + + + titleI18nKey: + noteLoadingBoundary.errorWhileLoadingContent + + + descriptionI18nKey: + CRAAAAASH + + + children: + +
+`; diff --git a/src/components/common/note-loading-boundary/hooks/use-load-note-from-server.ts b/src/components/common/note-loading-boundary/hooks/use-load-note-from-server.ts new file mode 100644 index 000000000..1b84b04ba --- /dev/null +++ b/src/components/common/note-loading-boundary/hooks/use-load-note-from-server.ts @@ -0,0 +1,28 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { useAsync } from 'react-use' +import { getNote } from '../../../../api/notes' +import { setNoteDataFromServer } from '../../../../redux/note-details/methods' +import { useSingleStringUrlParameter } from '../../../../hooks/common/use-single-string-url-parameter' +import type { AsyncState } from 'react-use/lib/useAsyncFn' + +/** + * Reads the note id from the current URL, requests the note from the backend and writes it into the global application state. + * + * @return An {@link AsyncState async state} that represents the current state of the loading process. + */ +export const useLoadNoteFromServer = (): AsyncState => { + const id = useSingleStringUrlParameter('noteId', undefined) + + return useAsync(async () => { + if (id === undefined) { + throw new Error('Invalid id') + } + const noteFromServer = await getNote(id) + setNoteDataFromServer(noteFromServer) + }, [id]) +} diff --git a/src/components/common/note-loading-boundary/note-loading-boundary.test.tsx b/src/components/common/note-loading-boundary/note-loading-boundary.test.tsx new file mode 100644 index 000000000..6b181cb8f --- /dev/null +++ b/src/components/common/note-loading-boundary/note-loading-boundary.test.tsx @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ +import * as useSingleStringUrlParameterModule from '../../../hooks/common/use-single-string-url-parameter' +import * as getNoteModule from '../../../api/notes' +import * as setNoteDataFromServerModule from '../../../redux/note-details/methods' +import type { Note } from '../../../api/notes/types' +import { Mock } from 'ts-mockery' +import { render, screen } from '@testing-library/react' +import { NoteLoadingBoundary } from './note-loading-boundary' +import { testId } from '../../../utils/test-id' +import { Fragment } from 'react' +import { mockI18n } from '../../markdown-renderer/test-utils/mock-i18n' +import * as CommonErrorPageModule from '../../error-pages/common-error-page' +import * as LoadingScreenModule from '../../../components/application-loader/loading-screen/loading-screen' + +describe('Note loading boundary', () => { + const mockedNoteId = 'mockedNoteId' + + afterEach(() => { + jest.resetAllMocks() + jest.resetModules() + }) + + beforeEach(async () => { + await mockI18n() + jest.spyOn(LoadingScreenModule, 'LoadingScreen').mockImplementation(({ errorMessage }) => { + return ( + + This is a mock for LoadingScreen. + errorMessage: {errorMessage} + + ) + }) + jest + .spyOn(CommonErrorPageModule, 'CommonErrorPage') + .mockImplementation(({ titleI18nKey, descriptionI18nKey, children }) => { + return ( + + This is a mock for CommonErrorPage. + titleI18nKey: {titleI18nKey} + descriptionI18nKey: {descriptionI18nKey} + children: {children} + + ) + }) + mockGetNoteIdQueryParameter() + }) + + const mockGetNoteIdQueryParameter = () => { + const expectedQueryParameter = 'noteId' + jest.spyOn(useSingleStringUrlParameterModule, 'useSingleStringUrlParameter').mockImplementation((parameter) => { + expect(parameter).toBe(expectedQueryParameter) + return mockedNoteId + }) + } + + const mockGetNoteApiCall = (returnValue: Note) => { + jest.spyOn(getNoteModule, 'getNote').mockImplementation((id) => { + expect(id).toBe(mockedNoteId) + return new Promise((resolve) => { + setTimeout(() => resolve(returnValue), 0) + }) + }) + } + + const mockCrashingNoteApiCall = () => { + jest.spyOn(getNoteModule, 'getNote').mockImplementation((id) => { + expect(id).toBe(mockedNoteId) + return new Promise((resolve, reject) => { + setTimeout(() => reject(new Error('CRAAAAASH')), 0) + }) + }) + } + + const mockSetNoteInRedux = (expectedNote: Note): jest.SpyInstance => { + return jest.spyOn(setNoteDataFromServerModule, 'setNoteDataFromServer').mockImplementation((givenNote) => { + expect(givenNote).toBe(expectedNote) + }) + } + + it('loads a note', async () => { + const mockedNote: Note = Mock.of() + mockGetNoteApiCall(mockedNote) + const setNoteInReduxFunctionMock = mockSetNoteInRedux(mockedNote) + + const view = render( + + success! + + ) + await screen.findByTestId('LoadingScreen') + await screen.findByTestId('success') + expect(view.container).toMatchSnapshot() + expect(setNoteInReduxFunctionMock).toBeCalledWith(mockedNote) + }) + + it('shows an error', async () => { + const mockedNote: Note = Mock.of() + mockCrashingNoteApiCall() + const setNoteInReduxFunctionMock = mockSetNoteInRedux(mockedNote) + + const view = render( + + success! + + ) + await screen.findByTestId('LoadingScreen') + await screen.findByTestId('CommonErrorPage') + expect(view.container).toMatchSnapshot() + expect(setNoteInReduxFunctionMock).not.toBeCalled() + }) +}) diff --git a/src/components/common/note-loading-boundary/note-loading-boundary.tsx b/src/components/common/note-loading-boundary/note-loading-boundary.tsx new file mode 100644 index 000000000..8886af232 --- /dev/null +++ b/src/components/common/note-loading-boundary/note-loading-boundary.tsx @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { PropsWithChildren } from 'react' +import React, { Fragment } from 'react' +import { useLoadNoteFromServer } from './hooks/use-load-note-from-server' +import { LoadingScreen } from '../../application-loader/loading-screen/loading-screen' +import { CommonErrorPage } from '../../error-pages/common-error-page' + +/** + * Loads the note identified by the note-id in the URL. + * During the loading a {@link LoadingScreen loading screen} will be rendered instead of the child elements. + * The boundary also shows errors that occur during the loading process. + * + * @param children The react elements that will be shown when the loading was successful. + */ +export const NoteLoadingBoundary: React.FC> = ({ children }) => { + const { error, loading } = useLoadNoteFromServer() + + if (loading) { + return + } else if (error) { + return ( + + ) + } else { + return {children} + } +} diff --git a/src/components/document-read-only-page/ErrorWhileLoadingNoteAlert.tsx b/src/components/document-read-only-page/ErrorWhileLoadingNoteAlert.tsx deleted file mode 100644 index 054f5ca49..000000000 --- a/src/components/document-read-only-page/ErrorWhileLoadingNoteAlert.tsx +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import React from 'react' -import { Alert } from 'react-bootstrap' -import { Trans, useTranslation } from 'react-i18next' -import { ShowIf } from '../common/show-if/show-if' -import type { SimpleAlertProps } from '../common/simple-alert/simple-alert-props' - -export const ErrorWhileLoadingNoteAlert: React.FC = ({ show }) => { - useTranslation() - - return ( - - - - - -
- -
-
- ) -} diff --git a/src/components/document-read-only-page/LoadingNoteAlert.tsx b/src/components/document-read-only-page/LoadingNoteAlert.tsx deleted file mode 100644 index 3092740d8..000000000 --- a/src/components/document-read-only-page/LoadingNoteAlert.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import React from 'react' -import { Alert } from 'react-bootstrap' -import { Trans } from 'react-i18next' -import { ShowIf } from '../common/show-if/show-if' -import type { SimpleAlertProps } from '../common/simple-alert/simple-alert-props' - -export const LoadingNoteAlert: React.FC = ({ show }) => { - return ( - - - - - - ) -} diff --git a/src/components/editor-page/editor-page-content.tsx b/src/components/editor-page/editor-page-content.tsx index c97f2fdff..8ba30a2bc 100644 --- a/src/components/editor-page/editor-page-content.tsx +++ b/src/components/editor-page/editor-page-content.tsx @@ -9,12 +9,8 @@ import { useTranslation } from 'react-i18next' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' import { setCheckboxInMarkdownContent, updateNoteTitleByFirstHeading } from '../../redux/note-details/methods' 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' import { AppBar, AppBarMode } from './app-bar/app-bar' import { EditorMode } from './app-bar/editor-view-mode' -import { useLoadNoteFromServer } from './hooks/useLoadNoteFromServer' import { useViewModeShortcuts } from './hooks/useViewModeShortcuts' import { Sidebar } from './sidebar/sidebar' import { Splitter } from './splitter/splitter' @@ -31,10 +27,6 @@ import { NoteAndAppTitleHead } from '../layout/note-and-app-title-head' import equal from 'fast-deep-equal' import { EditorPane } from './editor-pane/editor-pane' -export interface EditorPagePathParams { - id: string -} - export enum ScrollSource { EDITOR = 'editor', RENDERER = 'renderer' @@ -94,9 +86,7 @@ export const EditorPageContent: React.FC = () => { useApplyDarkMode() useEditorModeFromUrl() - const [error, loading] = useLoadNoteFromServer() - - useUpdateLocalHistoryEntry(!error && !loading) + useUpdateLocalHistoryEntry() const setRendererToScrollSource = useCallback(() => { if (scrollSource.current !== ScrollSource.RENDERER) { @@ -146,22 +136,16 @@ export const EditorPageContent: React.FC = () => {
-
- - +
+ +
- -
- - -
-
) diff --git a/src/components/editor-page/hooks/useLoadNoteFromServer.ts b/src/components/editor-page/hooks/useLoadNoteFromServer.ts deleted file mode 100644 index d22cc3bec..000000000 --- a/src/components/editor-page/hooks/useLoadNoteFromServer.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import { getNote } from '../../../api/notes' -import { setNoteDataFromServer } from '../../../redux/note-details/methods' -import { Logger } from '../../../utils/logger' -import { useRouter } from 'next/router' -import { useAsync } from 'react-use' - -const log = new Logger('Load Note From Server') - -export const useLoadNoteFromServer = (): [boolean, boolean] => { - const { query } = useRouter() - - const { loading, error } = useAsync(() => { - const rawId = query.id - if (rawId === undefined) { - return Promise.reject('invalid id') - } - const id = typeof rawId === 'string' ? rawId : rawId[0] - - return getNote(id) - .then((note) => { - setNoteDataFromServer(note) - }) - .catch((error: Error) => { - log.error('Error while fetching note from server', error) - return Promise.reject(error) - }) - }, [query]) - - return [error !== undefined, loading] -} diff --git a/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts b/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts index 35f16cb1d..91d3305e4 100644 --- a/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts +++ b/src/components/editor-page/hooks/useUpdateLocalHistoryEntry.ts @@ -12,7 +12,7 @@ import { useApplicationState } from '../../../hooks/common/use-application-state import type { HistoryEntryWithOrigin } from '../../../api/history/types' import { HistoryEntryOrigin } from '../../../api/history/types' -export const useUpdateLocalHistoryEntry = (updateReady: boolean): void => { +export const useUpdateLocalHistoryEntry = (): void => { const id = useApplicationState((state) => state.noteDetails.id) const userExists = useApplicationState((state) => !!state.user) const currentNoteTitle = useApplicationState((state) => state.noteDetails.title) @@ -22,7 +22,7 @@ export const useUpdateLocalHistoryEntry = (updateReady: boolean): void => { const lastNoteTags = useRef([]) useEffect(() => { - if (!updateReady || userExists) { + if (userExists) { return } if (currentNoteTitle === lastNoteTitle.current && equal(currentNoteTags, lastNoteTags.current)) { @@ -46,5 +46,5 @@ export const useUpdateLocalHistoryEntry = (updateReady: boolean): void => { updateLocalHistoryEntry(id, entry) lastNoteTitle.current = currentNoteTitle lastNoteTags.current = currentNoteTags - }, [updateReady, id, userExists, currentNoteTitle, currentNoteTags]) + }, [id, userExists, currentNoteTitle, currentNoteTags]) } diff --git a/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts b/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts index f2e2f5c66..07398c9a9 100644 --- a/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts +++ b/src/components/render-page/window-post-message-communicator/window-post-message-communicator.ts @@ -120,7 +120,7 @@ export abstract class WindowPostMessageCommunicator< * @param handler The handler that processes messages with the given message type. */ public setHandler(messageType: R, handler: MaybeHandler): void { - this.log.debug('Set handler for', messageType) + this.log.debug(handler === undefined ? 'Unset' : 'Set', 'handler for', messageType) this.handlers[messageType] = handler as MaybeHandler } diff --git a/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts b/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts index 9e46fd06f..bd501a478 100644 --- a/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts +++ b/src/hooks/common/use-trimmed-note-markdown-content-without-frontmatter.ts @@ -6,20 +6,16 @@ import { useMemo } from 'react' import { useApplicationState } from './use-application-state' -import equal from 'fast-deep-equal' /** * Returns the markdown content from the global application state trimmed to the maximal note length and without the frontmatter lines. */ export const useTrimmedNoteMarkdownContentWithoutFrontmatter = (): string[] => { const maxLength = useApplicationState((state) => state.config.maxDocumentLength) - const markdownContent = useApplicationState( - (state) => ({ - lines: state.noteDetails.markdownContent.lines, - content: state.noteDetails.markdownContent.plain - }), - equal - ) + const markdownContent = useApplicationState((state) => ({ + lines: state.noteDetails.markdownContent.lines, + content: state.noteDetails.markdownContent.plain + })) const lineOffset = useApplicationState((state) => state.noteDetails.frontmatterRendererInfo.lineOffset) const trimmedLines = useMemo(() => { diff --git a/src/pages/n/[id].tsx b/src/pages/n/[noteId].tsx similarity index 68% rename from src/pages/n/[id].tsx rename to src/pages/n/[noteId].tsx index 0e3158137..471f22391 100644 --- a/src/pages/n/[id].tsx +++ b/src/pages/n/[noteId].tsx @@ -8,15 +8,18 @@ import React from 'react' import { EditorToRendererCommunicatorContextProvider } from '../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' import type { NextPage } from 'next' import { EditorPageContent } from '../../components/editor-page/editor-page-content' +import { NoteLoadingBoundary } from '../../components/common/note-loading-boundary/note-loading-boundary' /** * Renders a page that is used by the user to edit markdown notes. It contains the editor and a renderer. */ export const EditorPage: NextPage = () => { return ( - - - + + + + + ) } diff --git a/src/pages/p/[id].tsx b/src/pages/p/[noteId].tsx similarity index 64% rename from src/pages/p/[id].tsx rename to src/pages/p/[noteId].tsx index 66aa990df..1af2bc529 100644 --- a/src/pages/p/[id].tsx +++ b/src/pages/p/[noteId].tsx @@ -4,25 +4,20 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import React, { Fragment } from 'react' -import { useLoadNoteFromServer } from '../../components/editor-page/hooks/useLoadNoteFromServer' -import { ShowIf } from '../../components/common/show-if/show-if' +import React from 'react' import { EditorToRendererCommunicatorContextProvider } from '../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' import { SlideShowPageContent } from '../../components/slide-show-page/slide-show-page-content' import { NoteAndAppTitleHead } from '../../components/layout/note-and-app-title-head' +import { NoteLoadingBoundary } from '../../components/common/note-loading-boundary/note-loading-boundary' export const SlideShowPage: React.FC = () => { - const [error, loading] = useLoadNoteFromServer() - return ( - + - - - + - + ) } diff --git a/src/pages/s/[id].tsx b/src/pages/s/[noteId].tsx similarity index 59% rename from src/pages/s/[id].tsx rename to src/pages/s/[noteId].tsx index 70296583e..965c23185 100644 --- a/src/pages/s/[id].tsx +++ b/src/pages/s/[noteId].tsx @@ -7,37 +7,29 @@ import React from 'react' import { useApplyDarkMode } from '../../hooks/common/use-apply-dark-mode' import { MotdModal } from '../../components/common/motd-modal/motd-modal' -import { ShowIf } from '../../components/common/show-if/show-if' import { AppBar, AppBarMode } from '../../components/editor-page/app-bar/app-bar' -import { useLoadNoteFromServer } from '../../components/editor-page/hooks/useLoadNoteFromServer' -import { ErrorWhileLoadingNoteAlert } from '../../components/document-read-only-page/ErrorWhileLoadingNoteAlert' -import { LoadingNoteAlert } from '../../components/document-read-only-page/LoadingNoteAlert' import { EditorToRendererCommunicatorContextProvider } from '../../components/editor-page/render-context/editor-to-renderer-communicator-context-provider' import { UiNotifications } from '../../components/notifications/ui-notifications' import { DocumentReadOnlyPageContent } from '../../components/document-read-only-page/document-read-only-page-content' import { NoteAndAppTitleHead } from '../../components/layout/note-and-app-title-head' +import { NoteLoadingBoundary } from '../../components/common/note-loading-boundary/note-loading-boundary' /** * Renders a page that contains only the rendered document without an editor or realtime updates. */ export const DocumentReadOnlyPage: React.FC = () => { useApplyDarkMode() - const [error, loading] = useLoadNoteFromServer() return ( - - - -
- -
- - -
- + + + + +
+ - -
+
+
) }