diff --git a/CHANGELOG.md b/CHANGELOG.md index 543588ed7..4e58bf521 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -77,6 +77,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0 - Images that are currently uploading will be rendered as "uploading". - Code blocks with `plantuml` as language are rendered as [PlantUML](https://plantuml.com/) diagram using a configured render server. - File based motd that supports markdown without html. +- New notes can be created with a pre-given content when accessing `/new?content=Example%20content`. ### Changed diff --git a/locales/en.json b/locales/en.json index 9e7f3f1d5..ce3a3daf1 100644 --- a/locales/en.json +++ b/locales/en.json @@ -551,5 +551,15 @@ }, "motd": { "title": "Information" + }, + "errors": { + "notFound": { + "title": "404 - page not found", + "description": "The requested page either does not exist or you don't have permission to access it." + }, + "noteCreationFailed": { + "title": "Note could not be created", + "description": "The note could not be created. Maybe you don't have permission to create notes." + } } } diff --git a/src/components/common/async-loading-boundary.tsx b/src/components/common/async-loading-boundary.tsx index 364658a8b..bdb48ec0a 100644 --- a/src/components/common/async-loading-boundary.tsx +++ b/src/components/common/async-loading-boundary.tsx @@ -4,7 +4,7 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { PropsWithChildren } from 'react' +import type { PropsWithChildren, ReactNode } from 'react' import React, { Fragment } from 'react' import { Trans, useTranslation } from 'react-i18next' import { WaitSpinner } from './wait-spinner/wait-spinner' @@ -14,6 +14,7 @@ export interface AsyncLoadingBoundaryProps { loading: boolean error?: Error | boolean componentName: string + errorComponent?: ReactNode } /** @@ -23,16 +24,21 @@ export interface AsyncLoadingBoundaryProps { * @param loading Indicates that the component is currently loading. Setting this will show a spinner instead of the children. * @param error Indicates that an error occurred during the loading process. Setting this to any non-null value will show an error message instead of the children. * @param componentName The name of the component that is currently loading. It will be shown in the error message. + * @param errorComponent Optional component that will be used in case of an error instead of the default alert message. * @param children The child {@link ReactElement elements} that are only shown if the component isn't in loading or error state */ export const AsyncLoadingBoundary: React.FC> = ({ loading, error, componentName, + errorComponent, children }) => { useTranslation() if (error !== undefined && error !== false) { + if (errorComponent) { + return {errorComponent} + } return ( diff --git a/src/components/common/routing/not-found-error-screen.tsx b/src/components/common/routing/not-found-error-screen.tsx deleted file mode 100644 index 04353bd31..000000000 --- a/src/components/common/routing/not-found-error-screen.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - * - * SPDX-License-Identifier: AGPL-3.0-only - */ - -import type { PropsWithChildren } from 'react' -import React from 'react' -import { LandingLayout } from '../../landing-layout/landing-layout' - -export const NotFoundErrorScreen: React.FC> = () => { - return ( - -
-

- 404 Not Found oops. -

-
-
- ) -} diff --git a/src/components/editor-page/app-bar/new-note-button.tsx b/src/components/editor-page/app-bar/new-note-button.tsx index 8c200573e..118d457bc 100644 --- a/src/components/editor-page/app-bar/new-note-button.tsx +++ b/src/components/editor-page/app-bar/new-note-button.tsx @@ -1,5 +1,5 @@ /* - * 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 */ @@ -8,13 +8,16 @@ import React from 'react' import { ForkAwesomeIcon } from '../../common/fork-awesome/fork-awesome-icon' import { Trans, useTranslation } from 'react-i18next' import { Button } from 'react-bootstrap' +import Link from 'next/link' export const NewNoteButton: React.FC = () => { useTranslation() return ( - + + + ) } diff --git a/src/components/error-pages/common-error-page.tsx b/src/components/error-pages/common-error-page.tsx new file mode 100644 index 000000000..2a29170a6 --- /dev/null +++ b/src/components/error-pages/common-error-page.tsx @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import React from 'react' +import type { PropsWithChildren } from 'react' +import { LandingLayout } from '../landing-layout/landing-layout' +import { useTranslation } from 'react-i18next' + +export interface CommonErrorPageProps { + titleI18nKey: string + descriptionI18nKey?: string +} + +/** + * Renders a common customizable error page. + * + * @param titleI18nKey The translation key for the title of the error. + * @param descriptionI18nKey The translation key for the description of the error. Property is optional. + * @param children The optional child elements that will be displayed beneath the description. + */ +export const CommonErrorPage: React.FC> = ({ + titleI18nKey, + descriptionI18nKey, + children +}) => { + const { t } = useTranslation() + + return ( + +
+
+

{t(titleI18nKey)}

+
+ {descriptionI18nKey ? t(descriptionI18nKey) : null} + {children} +
+
+
+ ) +} diff --git a/src/pages/404.tsx b/src/pages/404.tsx index ba4935d3f..051afc2df 100644 --- a/src/pages/404.tsx +++ b/src/pages/404.tsx @@ -1,17 +1,16 @@ /* - * 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 from 'react' -import { NotFoundErrorScreen } from '../components/common/routing/not-found-error-screen' +import { CommonErrorPage } from '../components/error-pages/common-error-page' +import type { NextPage } from 'next' /** * Renders a hedgedoc themed 404 page */ -const Custom404: React.FC = () => { - return +const Custom404: NextPage = () => { + return } export default Custom404 diff --git a/src/pages/[id].tsx b/src/pages/[id].tsx index 25f27fa5c..808b97d03 100644 --- a/src/pages/[id].tsx +++ b/src/pages/[id].tsx @@ -1,16 +1,16 @@ /* - SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file) - - SPDX-License-Identifier: AGPL-3.0-only + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only */ import React from 'react' import { getNote } from '../api/notes' -import { NotFoundErrorScreen } from '../components/common/routing/not-found-error-screen' import type { NextPage } from 'next' import { useAsync } from 'react-use' import { Redirect } from '../components/common/redirect' import { useSingleStringUrlParameter } from '../hooks/common/use-single-string-url-parameter' +import { CommonErrorPage } from '../components/error-pages/common-error-page' /** * Redirects the user to the editor if the link is a root level direct link to a version 1 note. @@ -30,7 +30,7 @@ export const DirectLinkFallback: NextPage = () => { }) if (error !== undefined) { - return + return } else if (value !== undefined) { return } else { diff --git a/src/pages/api/mock-backend/private/notes/index.ts b/src/pages/api/mock-backend/private/notes/index.ts new file mode 100644 index 000000000..835995fbe --- /dev/null +++ b/src/pages/api/mock-backend/private/notes/index.ts @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { NextApiRequest, NextApiResponse } from 'next' +import { HttpMethod, respondToMatchingRequest } from '../../../../../handler-utils/respond-to-matching-request' +import type { Note } from '../../../../../api/notes/types' + +const handler = (req: NextApiRequest, res: NextApiResponse): void => { + respondToMatchingRequest( + HttpMethod.POST, + req, + res, + { + content: 'new note content', + metadata: { + id: 'featuresId', + version: 2, + viewCount: 0, + updatedAt: '2021-04-24T09:27:51.000Z', + createdAt: '2021-04-24T09:27:51.000Z', + updateUsername: null, + primaryAddress: 'features', + editedBy: [], + title: 'New note', + tags: ['hedgedoc', 'demo', 'react'], + description: 'Many features, such wow!', + aliases: [ + { + name: 'features', + primaryAlias: true, + noteId: 'featuresId' + } + ], + permissions: { + owner: 'tilman', + sharedToUsers: [ + { + username: 'molly', + canEdit: true + } + ], + sharedToGroups: [ + { + groupName: '_LOGGED_IN', + canEdit: false + } + ] + } + }, + editedByAtPosition: [] + }, + 201 + ) +} + +export default handler diff --git a/src/pages/new.tsx b/src/pages/new.tsx new file mode 100644 index 000000000..d141ccd39 --- /dev/null +++ b/src/pages/new.tsx @@ -0,0 +1,41 @@ +/* + * SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file) + * + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import type { NextPage } from 'next' +import { useSingleStringUrlParameter } from '../hooks/common/use-single-string-url-parameter' +import { useAsync } from 'react-use' +import { createNote } from '../api/notes' +import { AsyncLoadingBoundary } from '../components/common/async-loading-boundary' +import { Redirect } from '../components/common/redirect' +import { CommonErrorPage } from '../components/error-pages/common-error-page' + +/** + * Creates a new note, optionally including the passed content and redirects to that note. + */ +export const NewNotePage: NextPage = () => { + const newContent = useSingleStringUrlParameter('content', '') + + const { loading, error, value } = useAsync(() => { + return createNote(newContent) + }, [newContent]) + + return ( + + }> + {value ? : null} + + ) +} + +export default NewNotePage