docs: consolidate docs (#2182)

Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
Philip Molares 2022-07-21 22:36:46 +02:00 committed by GitHub
parent 8d46d7e39e
commit ecffebc43c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
307 changed files with 1474 additions and 487 deletions

View file

@ -10,9 +10,11 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Adds an alias to an existing note.
*
* @param noteIdOrAlias The note id or an existing alias for a note.
* @param newAlias The new alias.
* @return Information about the newly created alias.
* @throws {Error} when the api request wasn't successfull
*/
export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise<Alias> => {
const response = await new PostApiRequestBuilder<Alias, NewAliasDto>('alias')
@ -27,8 +29,10 @@ export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise
/**
* Marks a given alias as the primary one for a note.
* The former primary alias should be marked as non-primary by the backend automatically.
*
* @param alias The alias to mark as primary for its corresponding note.
* @return The updated information about the alias.
* @throws {Error} when the api request wasn't successfull
*/
export const markAliasAsPrimary = async (alias: string): Promise<Alias> => {
const response = await new PutApiRequestBuilder<Alias, PrimaryAliasDto>('alias/' + alias)
@ -41,7 +45,9 @@ export const markAliasAsPrimary = async (alias: string): Promise<Alias> => {
/**
* Removes a given alias from its corresponding note.
*
* @param alias The alias to remove from its note.
* @throws {Error} when the api request wasn't successful.
*/
export const deleteAlias = async (alias: string): Promise<void> => {
await new DeleteApiRequestBuilder('alias/' + alias).sendRequest()

View file

@ -7,7 +7,8 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Requests to log out the current user.
* @throws Error if logout is not possible.
*
* @throws {Error} if logout is not possible.
*/
export const doLogout = async (): Promise<void> => {
await new DeleteApiRequestBuilder('auth/logout').sendRequest()

View file

@ -9,11 +9,13 @@ import { AuthError } from './types'
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
/**
* Requests to login a user via LDAP credentials.
* Requests to log in a user via LDAP credentials.
*
* @param provider The identifier of the LDAP provider with which to login.
* @param username The username with which to try the login.
* @param password The password of the user.
* @throws {AuthError.INVALID_CREDENTIALS} if the LDAP provider denied the given credentials.
* @throws {Error} when the api request wasn't successfull
*/
export const doLdapLogin = async (provider: string, username: string, password: string): Promise<void> => {
await new PostApiRequestBuilder<void, LoginDto>('auth/ldap/' + provider)

View file

@ -10,10 +10,12 @@ import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-requ
/**
* Requests to do a local login with a provided username and password.
*
* @param username The username for which the login should be tried.
* @param password The password which should be used to log in.
* @throws {AuthError.INVALID_CREDENTIALS} when the username or password is wrong.
* @throws {AuthError.LOGIN_DISABLED} when the local login is disabled on the backend.
* @throws {Error} when the api request wasn't successful.
*/
export const doLocalLogin = async (username: string, password: string): Promise<void> => {
await new PostApiRequestBuilder<void, LoginDto>('auth/local/login')
@ -30,11 +32,13 @@ export const doLocalLogin = async (username: string, password: string): Promise<
/**
* Requests to register a new local user in the backend.
*
* @param username The username of the new user.
* @param displayName The display name of the new user.
* @param password The password of the new user.
* @throws {RegisterError.USERNAME_EXISTING} when there is already an existing user with the same username.
* @throws {RegisterError.REGISTRATION_DISABLED} when the registration of local users has been disabled on the backend.
* @throws {Error} when the api request wasn't successful.
*/
export const doLocalRegister = async (username: string, displayName: string, password: string): Promise<void> => {
await new PostApiRequestBuilder<void, RegisterDto>('auth/local')

View file

@ -29,7 +29,7 @@ export abstract class ApiRequestBuilderWithBody<ResponseType, RequestBodyType> e
*
* @param bodyData The data to use as request body. Will get stringified to JSON.
* @return The API request instance itself for chaining.
* @see {withBody}
* @see withBody
*/
withJsonBody(bodyData: RequestBodyType): this {
this.withHeader('Content-Type', 'application/json')

View file

@ -108,7 +108,7 @@ export abstract class ApiRequestBuilder<ResponseType> {
* Send the prepared API call as a GET request. A default status code of 200 is expected.
*
* @return The API response.
* @throws Error when the status code does not match the expected one or is defined as in the custom status code
* @throws {Error} when the status code does not match the expected one or is defined as in the custom status code
* error mapping.
*/
abstract sendRequest(): Promise<ApiResponse<ResponseType>>

View file

@ -11,14 +11,14 @@ import { ApiRequestBuilderWithBody } from './api-request-builder-with-body'
*
* @param ResponseType The type of the expected response. Defaults to no response body.
* @param RequestBodyType The type of the request body. Defaults to no request body.
* @see {ApiRequestBuilder}
* @see ApiRequestBuilder
*/
export class DeleteApiRequestBuilder<ResponseType = void, RequestBodyType = unknown> extends ApiRequestBuilderWithBody<
ResponseType,
RequestBodyType
> {
/**
* @see {ApiRequestBuilder#sendRequest}
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('DELETE', 204)

View file

@ -11,11 +11,11 @@ import type { ApiResponse } from '../api-response'
* Builder to construct a GET request to the API.
*
* @param ResponseType The type of the expected response.
* @see {ApiRequestBuilder}
* @see ApiRequestBuilder
*/
export class GetApiRequestBuilder<ResponseType> extends ApiRequestBuilder<ResponseType> {
/**
* @see {ApiRequestBuilder#sendRequest}
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('GET', 200)

View file

@ -11,14 +11,14 @@ import { ApiRequestBuilderWithBody } from './api-request-builder-with-body'
*
* @param ResponseType The type of the expected response.
* @param RequestBodyType The type of the request body
* @see {ApiRequestBuilder}
* @see ApiRequestBuilder
*/
export class PostApiRequestBuilder<ResponseType, RequestBodyType> extends ApiRequestBuilderWithBody<
ResponseType,
RequestBodyType
> {
/**
* @see {ApiRequestBuilder#sendRequest}
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('POST', 201)

View file

@ -11,14 +11,14 @@ import { ApiRequestBuilderWithBody } from './api-request-builder-with-body'
*
* @param ResponseType The type of the expected response.
* @param RequestBodyType The type of the request body
* @see {ApiRequestBuilder}
* @see ApiRequestBuilder
*/
export class PutApiRequestBuilder<ResponseType, RequestBodyType> extends ApiRequestBuilderWithBody<
ResponseType,
RequestBodyType
> {
/**
* @see {ApiRequestBuilder#sendRequest}
* @see ApiRequestBuilder#sendRequest
*/
sendRequest(): Promise<ApiResponse<ResponseType>> {
return this.sendRequestAndVerifyResponse('PUT', 200)

View file

@ -7,7 +7,15 @@
import { defaultConfig } from '../../default-config'
import { Mock } from 'ts-mockery'
export const expectFetch = (expectedUrl: string, expectedStatusCode: number, expectedOptions: RequestInit): void => {
/**
* Mock fetch api for tests.
* Check that the given url and options are present in the request and return the given status code.
*
* @param expectedUrl the url that should be requested
* @param requestStatusCode the status code the mocked request should return
* @param expectedOptions additional options
*/
export const expectFetch = (expectedUrl: string, requestStatusCode: number, expectedOptions: RequestInit): void => {
global.fetch = jest.fn((fetchUrl: RequestInfo | URL, fetchOptions?: RequestInit): Promise<Response> => {
expect(fetchUrl).toEqual(expectedUrl)
expect(fetchOptions).toStrictEqual({
@ -18,7 +26,7 @@ export const expectFetch = (expectedUrl: string, expectedStatusCode: number, exp
})
return Promise.resolve(
Mock.of<Response>({
status: expectedStatusCode
status: requestStatusCode
})
)
})

View file

@ -12,6 +12,7 @@ export class ApiResponse<ResponseType> {
/**
* Initializes a new API response instance based on an HTTP response.
*
* @param response The HTTP response from the fetch call.
*/
constructor(response: Response) {
@ -31,7 +32,7 @@ export class ApiResponse<ResponseType> {
* Returns the response as parsed JSON. An error will be thrown if the response is not JSON encoded.
*
* @return The parsed JSON response.
* @throws Error if the response is not JSON encoded.
* @throws {Error} if the response is not JSON encoded.
*/
async asParsedJsonObject(): Promise<ResponseType> {
if (!this.response.headers.get('Content-Type')?.startsWith('application/json')) {

View file

@ -9,7 +9,9 @@ import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-requ
/**
* Fetches the frontend config from the backend.
*
* @return The frontend config.
* @throws {Error} when the api request wasn't successful.
*/
export const getConfig = async (): Promise<Config> => {
const response = await new GetApiRequestBuilder<Config>('config').sendRequest()

View file

@ -9,8 +9,10 @@ import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-requ
/**
* Retrieves information about a group with a given name.
*
* @param groupName The name of the group.
* @return Information about the group.
* @throws {Error} when the api request wasn't successful.
*/
export const getGroup = async (groupName: string): Promise<GroupInfo> => {
const response = await new GetApiRequestBuilder<GroupInfo>('groups/' + groupName).sendRequest()

View file

@ -1,18 +1,30 @@
/*
* 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 type { HistoryEntry, HistoryEntryPutDto, HistoryEntryWithOrigin } from './types'
import { HistoryEntryOrigin } from './types'
export const addRemoteOriginToHistoryEntry = (entryDto: HistoryEntry): HistoryEntryWithOrigin => {
/**
* Transform a {@link HistoryEntry} into a {@link HistoryEntryWithOrigin}.
*
* @param entry the entry to build from
* @return the history entry with an origin
*/
export const addRemoteOriginToHistoryEntry = (entry: HistoryEntry): HistoryEntryWithOrigin => {
return {
...entryDto,
...entry,
origin: HistoryEntryOrigin.REMOTE
}
}
/**
* Create a {@link HistoryEntryPutDto} from a {@link HistoryEntry}.
*
* @param entry the entry to build the dto from
* @return the dto for the api
*/
export const historyEntryToHistoryEntryPutDto = (entry: HistoryEntry): HistoryEntryPutDto => {
return {
pinStatus: entry.pinStatus,

View file

@ -11,7 +11,9 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Fetches the remote history for the user from the server.
*
* @return The remote history entries of the user.
* @throws {Error} when the api request wasn't successful.
*/
export const getRemoteHistory = async (): Promise<HistoryEntry[]> => {
const response = await new GetApiRequestBuilder<HistoryEntry[]>('me/history').sendRequest()
@ -20,7 +22,9 @@ export const getRemoteHistory = async (): Promise<HistoryEntry[]> => {
/**
* Replaces the remote history of the user with the given history entries.
*
* @param entries The history entries to store remotely.
* @throws {Error} when the api request wasn't successful.
*/
export const setRemoteHistoryEntries = async (entries: HistoryEntryPutDto[]): Promise<void> => {
await new PostApiRequestBuilder<void, HistoryEntryPutDto[]>('me/history').withJsonBody(entries).sendRequest()
@ -28,8 +32,10 @@ export const setRemoteHistoryEntries = async (entries: HistoryEntryPutDto[]): Pr
/**
* Updates a remote history entry's pin state.
*
* @param noteIdOrAlias The note id for which to update the pinning state.
* @param pinStatus True when the note should be pinned, false otherwise.
* @throws {Error} when the api request wasn't successful.
*/
export const updateRemoteHistoryEntryPinStatus = async (
noteIdOrAlias: string,
@ -45,7 +51,9 @@ export const updateRemoteHistoryEntryPinStatus = async (
/**
* Deletes a remote history entry.
*
* @param noteIdOrAlias The note id or alias of the history entry to remove.
* @throws {Error} when the api request wasn't successful.
*/
export const deleteRemoteHistoryEntry = async (noteIdOrAlias: string): Promise<void> => {
await new DeleteApiRequestBuilder('me/history/' + noteIdOrAlias).sendRequest()
@ -53,6 +61,8 @@ export const deleteRemoteHistoryEntry = async (noteIdOrAlias: string): Promise<v
/**
* Deletes the complete remote history.
*
* @throws {Error} when the api request wasn't successful.
*/
export const deleteRemoteHistory = async (): Promise<void> => {
await new DeleteApiRequestBuilder('me/history').sendRequest()

View file

@ -11,8 +11,9 @@ import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-re
/**
* Returns metadata about the currently signed-in user from the API.
* @throws Error when the user is not signed-in.
*
* @return The user metadata.
* @throws {Error} when the user is not signed-in.
*/
export const getMe = async (): Promise<LoginUserInfo> => {
const response = await new GetApiRequestBuilder<LoginUserInfo>('me').sendRequest()
@ -21,6 +22,8 @@ export const getMe = async (): Promise<LoginUserInfo> => {
/**
* Deletes the current user from the server.
*
* @throws {Error} when the api request wasn't successful.
*/
export const deleteUser = async (): Promise<void> => {
await new DeleteApiRequestBuilder('me').sendRequest()
@ -28,7 +31,9 @@ export const deleteUser = async (): Promise<void> => {
/**
* Changes the display name of the current user.
*
* @param displayName The new display name to set.
* @throws {Error} when the api request wasn't successful.
*/
export const updateDisplayName = async (displayName: string): Promise<void> => {
await new PostApiRequestBuilder<void, ChangeDisplayNameDto>('me/profile')
@ -40,7 +45,9 @@ export const updateDisplayName = async (displayName: string): Promise<void> => {
/**
* Retrieves a list of media belonging to the user.
*
* @return List of media object information.
* @throws {Error} when the api request wasn't successful.
*/
export const getMyMedia = async (): Promise<MediaUpload[]> => {
const response = await new GetApiRequestBuilder<MediaUpload[]>('me/media').sendRequest()

View file

@ -9,8 +9,10 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Requests an image-proxy URL from the backend for a given image URL.
*
* @param imageUrl The image URL which should be proxied.
* @return The proxy URL for the image.
* @throws {Error} when the api request wasn't successful.
*/
export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyResponse> => {
const response = await new PostApiRequestBuilder<ImageProxyResponse, ImageProxyRequestDto>('media/proxy')
@ -23,9 +25,11 @@ export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyRespons
/**
* Uploads a media file to the backend.
*
* @param noteIdOrAlias The id or alias of the note from which the media is uploaded.
* @param media The binary media content.
* @return The URL of the uploaded media object.
* @throws {Error} when the api request wasn't successful.
*/
export const uploadFile = async (noteIdOrAlias: string, media: Blob): Promise<MediaUpload> => {
const postData = new FormData()
@ -40,7 +44,9 @@ export const uploadFile = async (noteIdOrAlias: string, media: Blob): Promise<Me
/**
* Deletes some uploaded media object.
*
* @param mediaId The identifier of the media object to delete.
* @throws {Error} when the api request wasn't successful.
*/
export const deleteUploadedMedia = async (mediaId: string): Promise<void> => {
await new DeleteApiRequestBuilder('media/' + mediaId).sendRequest()

View file

@ -11,8 +11,10 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Retrieves the content and metadata about the specified note.
*
* @param noteIdOrAlias The id or alias of the note.
* @return Content and metadata of the specified note.
* @throws {Error} when the api request wasn't successful.
*/
export const getNote = async (noteIdOrAlias: string): Promise<Note> => {
const response = await new GetApiRequestBuilder<Note>('notes/' + noteIdOrAlias)
@ -23,8 +25,10 @@ export const getNote = async (noteIdOrAlias: string): Promise<Note> => {
/**
* Returns a list of media objects associated with the specified note.
*
* @param noteIdOrAlias The id or alias of the note.
* @return List of media object metadata associated with specified note.
* @throws {Error} when the api request wasn't successful.
*/
export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUpload[]> => {
const response = await new GetApiRequestBuilder<MediaUpload[]>(`notes/${noteIdOrAlias}/media`).sendRequest()
@ -33,8 +37,10 @@ export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUploa
/**
* Creates a new note with a given markdown content.
*
* @param markdown The content of the new note.
* @return Content and metadata of the new note.
* @throws {Error} when the api request wasn't successful.
*/
export const createNote = async (markdown: string): Promise<Note> => {
const response = await new PostApiRequestBuilder<Note, void>('notes')
@ -46,9 +52,11 @@ export const createNote = async (markdown: string): Promise<Note> => {
/**
* Creates a new note with a given markdown content and a defined primary alias.
*
* @param markdown The content of the new note.
* @param primaryAlias The primary alias of the new note.
* @return Content and metadata of the new note.
* @throws {Error} when the api request wasn't successful.
*/
export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias: string): Promise<Note> => {
const response = await new PostApiRequestBuilder<Note, void>('notes/' + primaryAlias)
@ -60,7 +68,9 @@ export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias:
/**
* Deletes the specified note.
*
* @param noteIdOrAlias The id or alias of the note to delete.
* @throws {Error} when the api request wasn't successful.
*/
export const deleteNote = async (noteIdOrAlias: string): Promise<void> => {
await new DeleteApiRequestBuilder('notes/' + noteIdOrAlias).sendRequest()

View file

@ -10,9 +10,11 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Sets the owner of a note.
*
* @param noteId The id of the note.
* @param owner The username of the new owner.
* @return The updated note permissions.
* @return The updated {@link NotePermissions}.
* @throws {Error} when the api request wasn't successful.
*/
export const setNoteOwner = async (noteId: string, owner: string): Promise<NotePermissions> => {
const response = await new PutApiRequestBuilder<NotePermissions, OwnerChangeDto>(
@ -27,9 +29,12 @@ export const setNoteOwner = async (noteId: string, owner: string): Promise<NoteP
/**
* Sets a permission for one user of a note.
*
* @param noteId The id of the note.
* @param username The username of the user to set the permission for.
* @param canEdit true if the user should be able to update the note, false otherwise.
* @return The updated {@link NotePermissions}.
* @throws {Error} when the api request wasn't successful.
*/
export const setUserPermission = async (
noteId: string,
@ -48,9 +53,12 @@ export const setUserPermission = async (
/**
* Sets a permission for one group of a note.
*
* @param noteId The id of the note.
* @param groupName The name of the group to set the permission for.
* @param canEdit true if the group should be able to update the note, false otherwise.
* @return The updated {@link NotePermissions}.
* @throws {Error} when the api request wasn't successful.
*/
export const setGroupPermission = async (
noteId: string,
@ -69,8 +77,11 @@ export const setGroupPermission = async (
/**
* Removes the permissions of a note for a user.
*
* @param noteId The id of the note.
* @param username The name of the user to remove the permission of.
* @return The updated {@link NotePermissions}.
* @throws {Error} when the api request wasn't successful.
*/
export const removeUserPermission = async (noteId: string, username: string): Promise<NotePermissions> => {
const response = await new DeleteApiRequestBuilder<NotePermissions>(
@ -83,8 +94,11 @@ export const removeUserPermission = async (noteId: string, username: string): Pr
/**
* Removes the permissions of a note for a group.
*
* @param noteId The id of the note.
* @param groupName The name of the group to remove the permission of.
* @return The updated {@link NotePermissions}.
* @throws {Error} when the api request wasn't successful.
*/
export const removeGroupPermission = async (noteId: string, groupName: string): Promise<NotePermissions> => {
const response = await new DeleteApiRequestBuilder<NotePermissions>(

View file

@ -9,9 +9,11 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Retrieves a note revision while using a cache for often retrieved revisions.
*
* @param noteId The id of the note for which to fetch the revision.
* @param revisionId The id of the revision to fetch.
* @return The revision.
* @throws {Error} when the api request wasn't successful.
*/
export const getRevision = async (noteId: string, revisionId: number): Promise<RevisionDetails> => {
const response = await new GetApiRequestBuilder<RevisionDetails>(
@ -22,8 +24,10 @@ export const getRevision = async (noteId: string, revisionId: number): Promise<R
/**
* Retrieves a list of all revisions stored for a given note.
*
* @param noteId The id of the note for which to look up the stored revisions.
* @return A list of revision ids.
* @throws {Error} when the api request wasn't successful.
*/
export const getAllRevisions = async (noteId: string): Promise<RevisionMetadata[]> => {
const response = await new GetApiRequestBuilder<RevisionMetadata[]>(`notes/${noteId}/revisions`).sendRequest()
@ -32,7 +36,9 @@ export const getAllRevisions = async (noteId: string): Promise<RevisionMetadata[
/**
* Deletes all revisions for a note.
*
* @param noteIdOrAlias The id or alias of the note to delete all revisions for.
* @throws {Error} when the api request wasn't successful.
*/
export const deleteRevisionsForNote = async (noteIdOrAlias: string): Promise<void> => {
await new DeleteApiRequestBuilder(`notes/${noteIdOrAlias}/revisions`).sendRequest()

View file

@ -10,7 +10,9 @@ import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-ap
/**
* Retrieves the access tokens for the current user.
*
* @return List of access token metadata.
* @throws {Error} when the api request wasn't successful.
*/
export const getAccessTokenList = async (): Promise<AccessToken[]> => {
const response = await new GetApiRequestBuilder<AccessToken[]>('tokens').sendRequest()
@ -19,9 +21,11 @@ export const getAccessTokenList = async (): Promise<AccessToken[]> => {
/**
* Creates a new access token for the current user.
*
* @param label The user-defined label for the new access token.
* @param validUntil The user-defined expiry date of the new access token in milliseconds of unix time.
* @return The new access token metadata along with its secret.
* @throws {Error} when the api request wasn't successful.
*/
export const postNewAccessToken = async (label: string, validUntil: number): Promise<AccessTokenWithSecret> => {
const response = await new PostApiRequestBuilder<AccessTokenWithSecret, CreateAccessTokenDto>('tokens')
@ -35,7 +39,9 @@ export const postNewAccessToken = async (label: string, validUntil: number): Pro
/**
* Removes an access token from the current user account.
*
* @param keyId The key id of the access token to delete.
* @throws {Error} when the api request wasn't successful.
*/
export const deleteAccessToken = async (keyId: string): Promise<void> => {
await new DeleteApiRequestBuilder('tokens/' + keyId).sendRequest()

View file

@ -9,8 +9,10 @@ import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-requ
/**
* Retrieves information about a specific user while using a cache to avoid many requests for the same username.
*
* @param username The username of interest.
* @return Metadata about the requested user.
* @throws {Error} when the api request wasn't successful.
*/
export const getUser = async (username: string): Promise<UserInfo> => {
const response = await new GetApiRequestBuilder<UserInfo>('users/' + username).sendRequest()

View file

@ -1,8 +1,11 @@
/*
* 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
*/
/**
* Custom {@link Error} class for the {@link ApplicationLoader}.
*/
export class ApplicationLoaderError extends Error {
constructor(taskName: string) {
super(`The task ${taskName} failed`)

View file

@ -14,7 +14,13 @@ import { ApplicationLoaderError } from './application-loader-error'
const log = new Logger('ApplicationLoader')
export const ApplicationLoader: React.FC<PropsWithChildren<unknown>> = ({ children }) => {
/**
* Initializes the application and executes all the setup tasks.
* It renders a {@link LoadingScreen} while this is happening. If there are any error, they will be displayed in the {@link LoadingScreen}.
*
* @param children The children in the React dom that should be shown once the application is loaded.
*/
export const ApplicationLoader: React.FC<PropsWithChildren> = ({ children }) => {
const { error, loading } = useAsync(async () => {
const initTasks = createSetUpTaskList()
for (const task of initTasks) {

View file

@ -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
*/
@ -7,6 +7,9 @@
import { getConfig } from '../../../api/config'
import { setConfig } from '../../../redux/config/methods'
/**
* Get the {@link Config frontend config} and save it in the global application state.
*/
export const fetchFrontendConfig = async (): Promise<void> => {
const config = await getConfig()
if (!config) {

View file

@ -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
*/
@ -12,6 +12,9 @@ import { fetchFrontendConfig } from './fetch-frontend-config'
import { loadDarkMode } from './load-dark-mode'
import { isDevMode, isTestMode } from '../../../utils/test-modes'
/**
* Create a custom delay in the loading of the application.
*/
const customDelay: () => Promise<void> = async () => {
if (
(isDevMode || isTestMode) &&
@ -30,6 +33,9 @@ export interface InitTask {
task: () => Promise<void>
}
/**
* Create a list of tasks, that need to be fulfilled on startup.
*/
export const createSetUpTaskList = (): InitTask[] => {
return [
{

View file

@ -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
*/
@ -31,7 +31,8 @@ export const loadDarkMode = async (): Promise<void> => {
/**
* Tries to read the saved dark mode settings from the browser local storage.
*
* @return {@code true} if the local storage has saved that the user prefers dark mode. {@code false} if the user doesn't or if the value could be read from local storage.
* @return {@link true} if the local storage has saved that the user prefers dark mode.
* {@link false} if the user doesn't prefer dark mode or if the value couldn't be read from local storage.
*/
const fetchDarkModeFromLocalStorage = (): boolean => {
if (!isClientSideRendering()) {
@ -48,7 +49,9 @@ const fetchDarkModeFromLocalStorage = (): boolean => {
/**
* Tries to read the preferred dark mode setting from the browser settings.
*
* @return {@code true} if the browser has reported that the user prefers dark mode. {@code false} if the user doesn't or if the browser doesn't support the `prefers-color-scheme` media query.
* @return {@link true} if the browser has reported that the user prefers dark mode.
* {@link false} if the browser doesn't prefer dark mode.
* {@link undefined} if the browser doesn't support the `prefers-color-scheme` media query.
*/
const determineDarkModeBrowserSettings = (): DarkModeConfig | undefined => {
if (!isClientSideRendering()) {

View file

@ -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
*/
@ -12,6 +12,9 @@ import { Settings } from 'luxon'
import { initReactI18next } from 'react-i18next'
import { isDevMode } from '../../../utils/test-modes'
/**
* Set up the internationalisation framework i18n.
*/
export const setUpI18n = async (): Promise<void> => {
await i18nUse(
resourcesToBackend((language, namespace, callback) => {

View file

@ -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
*/
@ -14,6 +14,13 @@ export interface BrandingProps {
delimiter?: boolean
}
/**
* Show the branding of the HedgeDoc instance.
* This branding can either be a text, a logo or both (in that case the text is used as the title and alt of the image).
*
* @param inline If the logo should be using the inline-size or the regular-size css class.
* @param delimiter If the delimiter between the HedgeDoc logo and the branding should be shown.
*/
export const Branding: React.FC<BrandingProps> = ({ inline = false, delimiter = true }) => {
const branding = useApplicationState((state) => state.config.branding)
const showBranding = !!branding.name || !!branding.logo

View file

@ -1,91 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Cache } from './cache'
describe('Test caching functionality', () => {
let testCache: Cache<string, number>
beforeAll(() => {
jest.useFakeTimers()
})
afterAll(() => {
jest.useRealTimers()
})
beforeEach(() => {
testCache = new Cache<string, number>(1000)
})
it('initialize with right lifetime, no entry limit', () => {
const lifetime = 1000
const lifetimedCache = new Cache<string, string>(lifetime)
expect(lifetimedCache.entryLifetime).toEqual(lifetime)
expect(lifetimedCache.maxEntries).toEqual(0)
})
it('initialize with right lifetime, given entry limit', () => {
const lifetime = 1000
const maxEntries = 10
const limitedCache = new Cache<string, string>(lifetime, maxEntries)
expect(limitedCache.entryLifetime).toEqual(lifetime)
expect(limitedCache.maxEntries).toEqual(maxEntries)
})
it('entry exists after inserting', () => {
testCache.put('test', 123)
expect(testCache.has('test')).toBe(true)
})
it('entry does not exist prior inserting', () => {
expect(testCache.has('test')).toBe(false)
})
it('entry does expire', () => {
const shortLivingCache = new Cache<string, number>(2)
shortLivingCache.put('test', 123)
expect(shortLivingCache.has('test')).toBe(true)
setTimeout(() => {
expect(shortLivingCache.has('test')).toBe(false)
}, 2000)
})
it('entry value does not change', () => {
const testValue = Date.now()
testCache.put('test', testValue)
expect(testCache.get('test')).toEqual(testValue)
})
it('error is thrown on non-existent entry', () => {
const accessNonExistentEntry = () => {
testCache.get('test')
}
expect(accessNonExistentEntry).toThrow(Error)
})
it('newer item replaces older item', () => {
testCache.put('test', 123)
testCache.put('test', 456)
expect(testCache.get('test')).toEqual(456)
})
it('entry limit is respected', () => {
const limitedCache = new Cache<string, number>(1000, 2)
limitedCache.put('first', 1)
expect(limitedCache.has('first')).toBe(true)
expect(limitedCache.has('second')).toBe(false)
expect(limitedCache.has('third')).toBe(false)
limitedCache.put('second', 2)
expect(limitedCache.has('first')).toBe(true)
expect(limitedCache.has('second')).toBe(true)
expect(limitedCache.has('third')).toBe(false)
limitedCache.put('third', 3)
expect(limitedCache.has('first')).toBe(false)
expect(limitedCache.has('second')).toBe(true)
expect(limitedCache.has('third')).toBe(true)
})
})

View file

@ -1,50 +0,0 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
export interface CacheEntry<T> {
entryCreated: number
data: T
}
export class Cache<K, V> {
readonly entryLifetime: number
readonly maxEntries: number
private store = new Map<K, CacheEntry<V>>()
constructor(lifetime: number, maxEntries = 0) {
if (lifetime < 0) {
throw new Error('Cache entry lifetime can not be less than 0 seconds.')
}
this.entryLifetime = lifetime
this.maxEntries = maxEntries
}
has(key: K): boolean {
if (!this.store.has(key)) {
return false
}
const entry = this.store.get(key)
return !!entry && entry.entryCreated >= Date.now() - this.entryLifetime * 1000
}
get(key: K): V {
const entry = this.store.get(key)
if (!entry) {
throw new Error('This cache entry does not exist. Check with ".has()" before using ".get()".')
}
return entry.data
}
put(key: K, value: V): void {
if (this.maxEntries > 0 && this.store.size === this.maxEntries) {
this.store.delete(this.store.keys().next().value as K)
}
this.store.set(key, {
entryCreated: Date.now(),
data: value
})
}
}

View file

@ -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
*/
@ -21,7 +21,7 @@ export interface CopyableFieldProps {
const log = new Logger('CopyableField')
/**
* Provides an input field with an attached copy button and a share button (if supported by the browser)
* Provides an input field with an attached copy button and a share button (if supported by the browser).
*
* @param content The content to present
* @param shareOriginUrl The URL of the page to which the shared content should be linked. If this value is omitted then the share button won't be shown.

View file

@ -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
*/
@ -15,7 +15,10 @@ export interface CountdownButtonProps extends ButtonProps {
/**
* Button that starts a countdown on render and is only clickable after the countdown has finished.
*
* @param countdownStartSeconds The initial amount of seconds for the countdown.
* @param children The children that should be displayed after the countdown has elapsed.
* @param props Additional props given to the {@link Button}.
*/
export const CountdownButton: React.FC<CountdownButtonProps> = ({ countdownStartSeconds, children, ...props }) => {
const [secondsRemaining, setSecondsRemaining] = useState(countdownStartSeconds)

View file

@ -1,15 +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
*/
/**
* Download a given {@link BlobPart file} from memory<.
*
* @param data The file to download.
* @param fileName Which filename does the file have.
* @param mimeType What is the files mimetype.
*/
export const download = (data: BlobPart, fileName: string, mimeType: string): void => {
const file = new Blob([data], { type: mimeType })
downloadLink(URL.createObjectURL(file), fileName)
}
export const downloadLink = (url: string, fileName: string): void => {
const downloadLink = (url: string, fileName: string): void => {
const helperElement = document.createElement('a')
helperElement.href = url
helperElement.download = fileName

View file

@ -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
*/
@ -11,6 +11,7 @@ import { Trans, useTranslation } from 'react-i18next'
/**
* Renders an input field for the current password when changing passwords.
*
* @param onChange Hook that is called when the entered password changes.
*/
export const CurrentPasswordField: React.FC<CommonFieldProps> = ({ onChange }) => {

View file

@ -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
*/
@ -15,6 +15,7 @@ interface DisplayNameFieldProps extends CommonFieldProps {
/**
* Renders an input field for the display name when registering.
*
* @param onChange Hook that is called when the entered display name changes.
* @param value The currently entered display name.
* @param initialValue The initial input field value.

View file

@ -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
*/
@ -11,6 +11,7 @@ import { Trans, useTranslation } from 'react-i18next'
/**
* Renders an input field for the new password when registering.
*
* @param onChange Hook that is called when the entered password changes.
* @param value The currently entered password.
*/

View file

@ -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
*/
@ -15,6 +15,7 @@ interface PasswordAgainFieldProps extends CommonFieldProps {
/**
* Renders an input field for typing the new password again when registering.
*
* @param onChange Hook that is called when the entered retype of the password changes.
* @param value The currently entered retype of the password.
* @param password The password entered into the password input field.

View file

@ -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
*/
@ -11,6 +11,7 @@ import { Trans, useTranslation } from 'react-i18next'
/**
* Renders an input field for the username when registering.
*
* @param onChange Hook that is called when the entered username changes.
* @param value The currently entered username.
*/

View file

@ -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
*/
@ -15,6 +15,16 @@ export interface ForkAwesomeIconProps {
stacked?: boolean
}
/**
* Renders a fork awesome icon.
*
* @param icon The icon that should be rendered.
* @param fixedWidth If the icon should be rendered with a fixed width.
* @param size The size class the icon should be rendered in.
* @param className Additional classes the icon should get.
* @param stacked If the icon is part of a {@link ForkAwesomeStack stack}.
* @see https://forkaweso.me
*/
export const ForkAwesomeIcon: React.FC<ForkAwesomeIconProps> = ({
icon,
fixedWidth = false,

View file

@ -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
*/
@ -15,6 +15,12 @@ export interface ForkAwesomeStackProps {
children: ReactElement<ForkAwesomeIconProps> | Array<ReactElement<ForkAwesomeIconProps>>
}
/**
* A stack of {@link ForkAwesomeIcon ForkAwesomeIcons}.
*
* @param size Which size the stack should have.
* @param children One or more {@link ForkAwesomeIcon ForkAwesomeIcons} to be stacked.
*/
export const ForkAwesomeStack: React.FC<ForkAwesomeStackProps> = ({ size, children }) => {
return (
<span className={`fa-stack ${size ? 'fa-' : ''}${size ?? ''}`}>

View file

@ -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
*/
@ -27,6 +27,12 @@ export enum HedgeDocLogoType {
WB_HORIZONTAL
}
/**
* Renders the HedgeDoc logo with the app name in different types.
*
* @param size The size the logo should have.
* @param logoType The logo type to be used.
*/
export const HedgeDocLogoWithText: React.FC<HedgeDocLogoProps> = ({ size = HedgeDocLogoSize.MEDIUM, logoType }) => {
const { t } = useTranslation()
const altText = useMemo(() => t('app.icon'), [t])

View file

@ -20,6 +20,16 @@ export interface IconButtonProps extends ButtonProps {
iconFixedWidth?: boolean
}
/**
* A generic {@link Button button} with an {@link ForkAwesomeIcon icon} in it.
*
* @param icon Which icon should be used
* @param children The children that will be added as the content of the button.
* @param iconFixedWidth If the icon should be of fixed width.
* @param border Should the button have a border.
* @param className Additional class names added to the button.
* @param props Additional props for the button.
*/
export const IconButton: React.FC<IconButtonProps> = ({
icon,
children,

View file

@ -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
*/
@ -13,6 +13,12 @@ export interface TranslatedIconButtonProps extends IconButtonProps {
i18nKey: string
}
/**
* Renders an {@link IconButton icon button} with a translation inside.
*
* @param i18nKey The key for the translated string.
* @param props Additional props directly given to the {@link IconButton}.
*/
export const TranslatedIconButton: React.FC<TranslatedIconButtonProps> = ({ i18nKey, ...props }) => {
return (
<IconButton {...props}>

View file

@ -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
*/
@ -10,6 +10,18 @@ import type { IconName } from '../fork-awesome/types'
import { ShowIf } from '../show-if/show-if'
import type { LinkWithTextProps } from './types'
/**
* An external link.
* This should be used for linking pages that are not part of the HedgeDoc instance.
* The links will be opened in a new tab.
*
* @param href The links location
* @param text The links text
* @param icon An optional icon to be shown before the links text
* @param id An id for the link object
* @param className Additional class names added to the link object
* @param title The title of the link
*/
export const ExternalLink: React.FC<LinkWithTextProps> = ({
href,
text,

View file

@ -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
*/
@ -11,6 +11,17 @@ import type { IconName } from '../fork-awesome/types'
import { ShowIf } from '../show-if/show-if'
import type { LinkWithTextProps } from './types'
/**
* An internal link.
* This should be used for linking pages of the HedgeDoc instance.
*
* @param href The links location
* @param text The links text
* @param icon An optional icon to be shown before the links text
* @param id An id for the link object
* @param className Additional class names added to the link object
* @param title The title of the link
*/
export const InternalLink: React.FC<LinkWithTextProps> = ({
href,
text,

View file

@ -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
*/
@ -9,6 +9,13 @@ import { useTranslation } from 'react-i18next'
import { ExternalLink } from './external-link'
import type { TranslatedLinkProps } from './types'
/**
* An {@link ExternalLink external link} with translated text.
*
* @param i18nKey The key of the translation
* @param i18nOption The translation options
* @param props Additional props directly given to the {@link ExternalLink}
*/
export const TranslatedExternalLink: React.FC<TranslatedLinkProps> = ({ i18nKey, i18nOption, ...props }) => {
const { t } = useTranslation()
return <ExternalLink text={t(i18nKey, i18nOption)} {...props} />

View file

@ -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,7 +8,13 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { InternalLink } from './internal-link'
import type { TranslatedLinkProps } from './types'
/**
* An {@link InternalLink internal link} with translated text.
*
* @param i18nKey The key of the translation
* @param i18nOption The translation options
* @param props Additional props directly given to the {@link InternalLink}
*/
export const TranslatedInternalLink: React.FC<TranslatedLinkProps> = ({ i18nKey, i18nOption, ...props }) => {
const { t } = useTranslation()
return <InternalLink text={t(i18nKey, i18nOption)} {...props} />

View file

@ -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
*/
@ -14,6 +14,13 @@ export interface LockButtonProps {
title: string
}
/**
* A button with a lock icon.
*
* @param locked If the button should be shown as locked or not
* @param onLockedChanged The callback to change the state from locked to unlocked and vise-versa.
* @param title The title for the button.
*/
export const LockButton: React.FC<LockButtonProps> = ({ locked, onLockedChanged, title }) => {
return (
<Button variant='dark' size='sm' onClick={() => onLockedChanged(!locked)} title={title}>

View file

@ -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
*/
@ -30,6 +30,20 @@ export interface ModalContentProps {
export type CommonModalProps = PropsWithDataCypressId & ModalVisibilityProps & ModalContentProps
/**
* Renders a generic modal.
*
* @param show If the modal should be shown or not.
* @param onHide The callback to hide the modal again
* @param title The title in the header of the modal
* @param showCloseButton If a close button should be shown
* @param titleIcon An optional title icon
* @param additionalClasses Additional class names for the modal
* @param modalSize The modal size
* @param children The children to render into the modal.
* @param titleIsI18nKey If the title is a i18n key and should be used as such
* @param props Additional props directly given to the modal
*/
export const CommonModal: React.FC<PropsWithChildren<CommonModalProps>> = ({
show,
onHide,

View file

@ -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
*/
@ -17,6 +17,19 @@ export interface DeletionModalProps extends CommonModalProps {
deletionButtonI18nKey: string
}
/**
* Renders a generic modal for deletion.
* This means in addition to most things for the {@link CommonModal} there is also a button to confirm the deletion and a corresponding callback.
*
* @param show If the modal should be shown or not.
* @param onHide The callback to hide the modal again
* @param title The title in the header of the modal
* @param onConfirm The callback for the delete button.
* @param deletionButtonI18nKey The i18n key for the deletion button.
* @param titleIcon An optional title icon
* @param children The children to render into the modal.
* @param props Additional props directly given to the modal
*/
export const DeletionModal: React.FC<PropsWithChildren<DeletionModalProps>> = ({
show,
onHide,

View file

@ -1,9 +1,15 @@
/*
* 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
*/
/**
* Create an array with numbers from 1 to n.
*
* @param length The length of the array (or the number n)
* @return An array of numbers from 1 to n
*/
export const createNumberRangeArray = (length: number): number[] => {
return Array.from(Array(length).keys())
}

View file

@ -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
*/
@ -11,6 +11,12 @@ export interface PageItemProps {
index: number
}
/**
* Renders a number and adds an onClick handler to it.
*
* @param index The number to render
* @param onClick The onClick Handler
*/
export const PagerItem: React.FC<PageItemProps> = ({ index, onClick }) => {
return (
<li className='page-item'>

View file

@ -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
*/
@ -15,6 +15,13 @@ export interface PaginationProps {
lastPageIndex: number
}
/**
* Renders a pagination menu to move back and forth between pages.
*
* @param numberOfPageButtonsToShowAfterAndBeforeCurrent The number of buttons that should be shown before and after the current button.
* @param onPageChange The callback when one of the buttons is clicked
* @param lastPageIndex The index of the last page
*/
export const PagerPagination: React.FC<PaginationProps> = ({
numberOfPageButtonsToShowAfterAndBeforeCurrent,
onPageChange,

View file

@ -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
*/
@ -13,6 +13,14 @@ export interface PagerPageProps {
onLastPageIndexChange: (lastPageIndex: number) => void
}
/**
* Renders a limited number of the given children.
*
* @param children The children to render
* @param numberOfElementsPerPage The number of elements per page
* @param pageIndex Which page of the children to render
* @param onLastPageIndexChange A callback to notify about changes to the maximal page number
*/
export const Pager: React.FC<PropsWithChildren<PagerPageProps>> = ({
children,
numberOfElementsPerPage,

View file

@ -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
*/
@ -11,6 +11,12 @@ export interface ShowIfProps {
condition: boolean
}
/**
* Renders the children if the condition is met.
*
* @param children The children to show if the condition is met.
* @param condition If the children should be shown
*/
export const ShowIf: React.FC<PropsWithChildren<ShowIfProps>> = ({ children, condition }) => {
return condition ? <Fragment>{children}</Fragment> : null
}

View file

@ -21,9 +21,10 @@ export interface UserAvatarForUsernameProps extends Omit<UserAvatarProps, 'user'
* Renders the user avatar for a given username.
* When no username is given, the guest user will be used as fallback.
*
* @see {UserAvatar}
* @see UserAvatar
*
* @param username The username for which to show the avatar or null to show the guest user avatar.
* @param props Additional props directly given to the {@link UserAvatar}
*/
export const UserAvatarForUsername: React.FC<UserAvatarForUsernameProps> = ({ username, ...props }) => {
const { t } = useTranslation()

View file

@ -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
*/
@ -7,6 +7,9 @@
import React from 'react'
import { ForkAwesomeIcon } from '../fork-awesome/fork-awesome-icon'
/**
* Renders a indefinitely spinning spinner.
*/
export const WaitSpinner: React.FC = () => {
return (
<div className={'m-3 d-flex align-items-center justify-content-center'}>

View file

@ -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
*/
@ -13,7 +13,7 @@ import { NoteInfoLineCreated } from '../editor-page/document-bar/note-info/note-
import { NoteInfoLineUpdated } from '../editor-page/document-bar/note-info/note-info-line-updated'
/**
* Renders an infobar with metadata about the current note.
* Renders an info bar with metadata about the current note.
*/
export const DocumentInfobar: React.FC = () => {
const { t } = useTranslation()

View file

@ -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
*/
@ -29,6 +29,11 @@ export interface AppBarProps {
mode: AppBarMode
}
/**
* Renders the app bar.
*
* @param mode Which mode the app bar should be rendered in. This mainly adds / removes buttons for the editor.
*/
export const AppBar: React.FC<AppBarProps> = ({ mode }) => {
const userExists = useApplicationState((state) => !!state.user)
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)

View file

@ -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
*/
@ -16,6 +16,9 @@ enum DarkModeState {
LIGHT
}
/**
* Renders a button group to activate / deactivate the dark mode.
*/
const DarkModeButton: React.FC = () => {
const { t } = useTranslation()
const darkModeEnabled = useIsDarkModeActivated() ? DarkModeState.DARK : DarkModeState.LIGHT

View file

@ -18,6 +18,10 @@ export enum EditorMode {
EDITOR = 'edit'
}
/**
* Renders the button group to set the editor mode.
* @see EditorMode
*/
export const EditorViewMode: React.FC = () => {
const { t } = useTranslation()
const editorMode = useApplicationState((state) => state.editorConfig.editorMode)

View file

@ -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
*/
@ -17,6 +17,13 @@ const HighlightedCode = React.lazy(
)
const DocumentMarkdownRenderer = React.lazy(() => import('../../../markdown-renderer/document-markdown-renderer'))
/**
* Renders one line in the {@link CheatsheetTabContent cheat sheet}.
* This line shows an minimal markdown example and how it would be rendered.
*
* @param markdown The markdown to be shown and rendered
* @param onTaskCheckedChange A callback to call if a task would be clicked
*/
export const CheatsheetLine: React.FC<CheatsheetLineProps> = ({ markdown, onTaskCheckedChange }) => {
const lines = useMemo(() => markdown.split('\n'), [markdown])
const checkboxClick = useCallback(

View file

@ -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
*/
@ -10,6 +10,9 @@ import { Trans, useTranslation } from 'react-i18next'
import { CheatsheetLine } from './cheatsheet-line'
import styles from './cheatsheet.module.scss'
/**
* Renders the content of the cheat sheet for the {@link HelpModal}.
*/
export const CheatsheetTabContent: React.FC = () => {
const { t } = useTranslation()
const [checked, setChecked] = useState<boolean>(false)

View file

@ -12,6 +12,9 @@ import { HelpModal } from './help-modal'
import { cypressId } from '../../../../utils/cypress-attribute'
import { useBooleanState } from '../../../../hooks/common/use-boolean-state'
/**
* Renders the button to open the {@link HelpModal}.
*/
export const HelpButton: React.FC = () => {
const { t } = useTranslation()
const [modalVisibility, showModal, closeModal] = useBooleanState()

View file

@ -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
*/
@ -19,6 +19,17 @@ export enum HelpTabStatus {
Links = 'links.title'
}
/**
* Renders the help modal.
* This modal shows the user the markdown cheatsheet, shortcuts and different links with further help.
*
* @see CheatsheetTabContent
* @see ShortcutTabContent
* @see LinksTabContent
*
* @param show If the modal should be shown
* @param onHide A callback when the modal should be closed again
*/
export const HelpModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
const [tab, setTab] = useState<HelpTabStatus>(HelpTabStatus.Cheatsheet)
const { t } = useTranslation()

View file

@ -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
*/
@ -11,6 +11,9 @@ import links from '../../../../links.json'
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
import { TranslatedInternalLink } from '../../../common/links/translated-internal-link'
/**
* Renders a bunch of links, where further help can be requested.
*/
export const LinksTabContent: React.FC = () => {
useTranslation()

View file

@ -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
*/
@ -9,6 +9,9 @@ import { Card, ListGroup, Row } from 'react-bootstrap'
import { Trans } from 'react-i18next'
import { isMac } from '../../utils'
/**
* Renders a list of shortcuts usable in HedgeDoc.
*/
export const ShortcutTabContent: React.FC = () => {
const modifierKey = useMemo(() => (isMac() ? <kbd></kbd> : <kbd>Ctrl</kbd>), [])
const altKey = useMemo(() => (isMac() ? <kbd></kbd> : <kbd>Alt</kbd>), [])

View file

@ -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
*/
@ -15,6 +15,9 @@ import {
HedgeDocLogoWithText
} from '../../common/hedge-doc-logo/hedge-doc-logo-with-text'
/**
* Renders the branding for the {@link AppBar}
*/
export const NavbarBranding: React.FC = () => {
const darkModeActivated = useIsDarkModeActivated()

View file

@ -10,6 +10,9 @@ import { Trans, useTranslation } from 'react-i18next'
import { Button } from 'react-bootstrap'
import Link from 'next/link'
/**
* Renders a button to create a new note.
*/
export const NewNoteButton: React.FC = () => {
useTranslation()

View file

@ -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
*/
@ -18,6 +18,10 @@ enum SyncScrollState {
UNSYNCED
}
/**
* Renders a button group with two states for the sync scroll buttons.
* This makes it possible to activate or deactivate sync scrolling.
*/
export const SyncScrollButtons: React.FC = () => {
const syncScrollEnabled = useApplicationState((state) => state.editorConfig.syncScroll)
? SyncScrollState.SYNCED

View file

@ -24,7 +24,9 @@ type ChangeEditorContentContext = [CodeMirrorReference, SetCodeMirrorReference]
const changeEditorContentContext = createContext<ChangeEditorContentContext | undefined>(undefined)
/**
* Extracts the code mirror reference from the parent context
* Extracts the {@link CodeMirrorReference code mirror reference} from the parent context.
*
* @return The {@link CodeMirrorReference} from the parent context.
*/
export const useCodeMirrorReference = (): CodeMirrorReference => {
const contextContent = Optional.ofNullable(useContext(changeEditorContentContext)).orElseThrow(
@ -34,7 +36,9 @@ export const useCodeMirrorReference = (): CodeMirrorReference => {
}
/**
* Extracts the code mirror reference from the parent context
* Provides a function to set the {@link CodeMirrorReference code mirror reference} in the current context.
*
* @return A function to set a {@link CodeMirrorReference code mirror reference}.
*/
export const useSetCodeMirrorReference = (): SetCodeMirrorReference => {
const contextContent = Optional.ofNullable(useContext(changeEditorContentContext)).orElseThrow(

View file

@ -13,6 +13,7 @@ import { DateTime } from 'luxon'
/**
* Renders an info line about the creation of the current note.
*
* @param size The size in which the line should be displayed.
*/
export const NoteInfoLineCreated: React.FC<NoteInfoTimeLineProps> = ({ size }) => {

View file

@ -15,6 +15,7 @@ import { DateTime } from 'luxon'
/**
* Renders an info line about the last update of the current note.
*
* @param size The size in which line and user avatar should be displayed.
*/
export const NoteInfoLineUpdated: React.FC<NoteInfoTimeLineProps> = ({ size }) => {

View file

@ -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
*/
@ -14,6 +14,14 @@ export interface NoteInfoLineProps {
size?: '2x' | '3x' | '4x' | '5x' | undefined
}
/**
* This is the base component for all note info lines.
* It renders an icon and some children in italic.
*
* @param icon The icon be shown
* @param size Which size the icon should be
* @param children The children to render
*/
export const NoteInfoLine: React.FC<PropsWithChildren<NoteInfoLineProps>> = ({ icon, size, children }) => {
return (
<span className={'d-flex align-items-center'}>

View file

@ -17,6 +17,7 @@ import { NoteInfoLineContributors } from './note-info-line-contributors'
/**
* Modal that shows informational data about the current note.
*
* @param show true when the modal should be visible, false otherwise.
* @param onHide Callback that is fired when the modal is going to be closed.
*/

View file

@ -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
*/
@ -12,6 +12,11 @@ export interface TimeFromNowProps {
time: DateTime
}
/**
* Renders a given time relative to the current time.
*
* @param time The time to be rendered.
*/
export const TimeFromNow: React.FC<TimeFromNowProps> = ({ time }) => {
return (
<time className={'mx-1'} title={time.toFormat('DDDD T')} dateTime={time.toString()}>

View file

@ -6,15 +6,19 @@
import type { PropsWithChildren } from 'react'
import React from 'react'
import type { PropsWithDataCypressId } from '../../../../utils/cypress-attribute'
import { cypressId } from '../../../../utils/cypress-attribute'
export interface UnitalicBoldContentProps {
export interface UnitalicBoldContentProps extends PropsWithDataCypressId {
text?: string | number
}
/**
* Renders the children elements in a non-italic but bold style.
*
* @param text Optional text content that should be rendered.
* @param children Children that may be rendered.
* @param props Additional props for cypressId
*/
export const UnitalicBoldContent: React.FC<PropsWithChildren<UnitalicBoldContentProps>> = ({
text,

View file

@ -17,6 +17,7 @@ export interface PermissionAddEntryFieldProps {
/**
* Permission entry row containing a field for adding new user permission entries.
*
* @param onAddEntry Callback that is fired with the entered username as identifier of the entry to add.
* @param i18nKey The localization key for the submit button.
*/

View file

@ -31,6 +31,7 @@ export interface PermissionEntryButtonsProps {
/**
* Buttons next to a user or group permission entry to change the permissions or remove the entry.
*
* @param name The name of the user or group.
* @param type The type of the entry. Either {@link PermissionType.USER} or {@link PermissionType.GROUP}.
* @param currentSetting How the permission is currently set.

View file

@ -20,6 +20,7 @@ export interface PermissionEntrySpecialGroupProps {
/**
* Permission entry that represents one of the built-in special groups.
*
* @param level The access level that is currently set for the group.
* @param type The type of the special group. Must be either {@link SpecialGroup.EVERYONE} or {@link SpecialGroup.LOGGED_IN}.
*/

View file

@ -22,6 +22,7 @@ export interface PermissionEntryUserProps {
/**
* Permission entry for a user that can be set to read-only or writeable and can be removed.
*
* @param entry The permission entry.
*/
export const PermissionEntryUser: React.FC<PermissionEntryUserProps> = ({ entry }) => {

View file

@ -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
*/
@ -14,6 +14,7 @@ import { PermissionSectionSpecialGroups } from './permission-section-special-gro
/**
* Modal for viewing and managing the permissions of the note.
*
* @param show true to show the modal, false otherwise.
* @param onHide Callback that is fired when the modal is about to be closed.
*/

View file

@ -14,6 +14,11 @@ export interface PermissionOwnerChangeProps {
onConfirmOwnerChange: (newOwner: string) => void
}
/**
* Renders an input group to change the permission owner.
*
* @param onConfirmOwnerChange The callback to call if the owner was changed.
*/
export const PermissionOwnerChange: React.FC<PermissionOwnerChangeProps> = ({ onConfirmOwnerChange }) => {
const { t } = useTranslation()
const [ownerFieldValue, setOwnerFieldValue] = useState('')

View file

@ -17,6 +17,7 @@ export interface PermissionOwnerInfoProps {
/**
* Content for the owner section of the permission modal that shows the current note owner.
*
* @param onEditOwner Callback that is fired when the user chooses to change the note owner.
*/
export const PermissionOwnerInfo: React.FC<PermissionOwnerInfoProps> = ({ onEditOwner }) => {

View file

@ -19,6 +19,7 @@ export interface RevisionModalFooterProps {
/**
* Renders the footer of the revision modal that includes buttons to download the currently selected revision or to
* revert the note content back to that revision.
*
* @param selectedRevisionId The currently selected revision id or undefined if no revision was selected.
* @param onHide Callback that is fired when the modal is about to be closed.
*/

View file

@ -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
*/
@ -13,6 +13,7 @@ const DISPLAY_MAX_USERS_PER_REVISION = 9
/**
* Downloads a given revision's content as markdown document in the browser.
*
* @param noteId The id of the note from which to download the revision.
* @param revision The revision details object containing the content to download.
*/
@ -25,6 +26,7 @@ export const downloadRevision = (noteId: string, revision: RevisionDetails | nul
/**
* Fetches user details for the given usernames while returning a maximum of 9 users.
*
* @param usernames The list of usernames to fetch.
* @throws {Error} in case the user-data request failed.
* @return An array of user details.

View file

@ -15,6 +15,12 @@ import { useApplicationState } from '../../../../hooks/common/use-application-st
import { NoteType } from '../../../../redux/note-details/types/note-details'
import { useFrontendBaseUrl } from '../../../../hooks/common/use-frontend-base-url'
/**
* Renders a modal which provides shareable URLs of this note.
*
* @param show If the modal should be shown
* @param onHide The callback when the modal should be closed
*/
export const ShareModal: React.FC<ModalVisibilityProps> = ({ show, onHide }) => {
useTranslation()
const noteFrontmatter = useApplicationState((state) => state.noteDetails.frontmatter)

View file

@ -37,6 +37,15 @@ import { useOnFirstEditorUpdateExtension } from './hooks/yjs/use-on-first-editor
import { useIsConnectionSynced } from './hooks/yjs/use-is-connection-synced'
import { useMarkdownContentYText } from './hooks/yjs/use-markdown-content-y-text'
/**
* Renders the text editor pane of the editor.
* The used editor is {@link ReactCodeMirror code mirror}.
*
* @param scrollState The current {@link ScrollState}
* @param onScroll The callback to update the {@link ScrollState}
* @param onMakeScrollSource The callback to request to become the scroll source.
* @external {ReactCodeMirror} https://npmjs.com/@uiw/react-codemirror
*/
export const EditorPane: React.FC<ScrollProps> = ({ scrollState, onScroll, onMakeScrollSource }) => {
const ligaturesEnabled = useApplicationState((state) => state.editorConfig.ligatures)

View file

@ -9,7 +9,7 @@
*
* @param markdownContent The markdown content whose content should be checked
* @param cursorPosition The cursor position that may or may not be in a code fence
* @return {@code true} if the given cursor position is in a code fence
* @return {@link true} if the given cursor position is in a code fence
*/
export const isCursorInCodeFence = (markdownContent: string, cursorPosition: number): boolean => {
const lines = markdownContent.slice(0, cursorPosition).split('\n')

View file

@ -8,7 +8,9 @@ import { createNumberRangeArray } from '../../../../common/number-range/number-r
/**
* Checks if the given text is a tab-and-new-line-separated table.
*
* @param text The text to check
* @return If the text is a table or not
*/
export const isTable = (text: string): boolean => {
// Tables must consist of multiple rows and columns
@ -32,7 +34,8 @@ export const isTable = (text: string): boolean => {
}
/**
* Reformat the given text as Markdown table
* Reformat the given text as Markdown table.
*
* @param pasteData The plain text table separated by tabs and new-lines
* @return the formatted Markdown table
*/

View file

@ -34,7 +34,6 @@ const applyScrollState = (view: EditorView, scrollState: ScrollState): void => {
/**
* Monitors the given scroll state and scrolls the editor to the state if changed.
*
* @param editorRef The editor that should be manipulated
* @param scrollState The scroll state that should be monitored
*/
export const useApplyScrollState = (scrollState?: ScrollState): void => {

View file

@ -11,7 +11,7 @@ import { EditorView } from '@codemirror/view'
import type { Extension, SelectionRange } from '@codemirror/state'
/**
* Provides a callback for codemirror that handles cursor changes
* Provides a callback for codemirror that handles cursor changes.
*
* @return the generated callback
*/

View file

@ -19,7 +19,7 @@ import type { ContentFormatter } from '../../change-content-context/change-conte
import { useCodeMirrorReference } from '../../change-content-context/change-content-context'
/**
* Processes the upload of the given file and inserts the correct Markdown code
* Processes the upload of the given file and inserts the correct Markdown code.
*
* @param view the codemirror instance that is used to insert the Markdown code
* @param file The file to upload

View file

@ -22,7 +22,7 @@ const calculateLineBasedPosition = (absolutePosition: number, lineStartIndexes:
}
/**
* Returns the line+character based position of the to cursor, if available.
* Returns the line+character based position of the to-cursor, if available.
*/
export const useLineBasedToPosition = (): LineBasedPosition | undefined => {
const lineStartIndexes = useApplicationState((state) => state.noteDetails.markdownContent.lineStartIndexes)
@ -38,7 +38,7 @@ export const useLineBasedToPosition = (): LineBasedPosition | undefined => {
}
/**
* Returns the line+character based position of the from cursor.
* Returns the line+character based position of the from-cursor.
*/
export const useLineBasedFromPosition = (): LineBasedPosition => {
const lineStartIndexes = useApplicationState((state) => state.noteDetails.markdownContent.lineStartIndexes)

View file

@ -10,7 +10,7 @@ import type { YText } from 'yjs/dist/src/types/YText'
/**
* One-Way-synchronizes the text of the given {@link YText y-text} into the global application state.
*4
*
* @param yText The source text
*/
export const useBindYTextToRedux = (yText: YText): void => {

View file

@ -11,6 +11,7 @@ import type { YDocMessageTransporter } from '@hedgedoc/realtime'
* Checks if the given message transporter has received at least one full synchronisation.
*
* @param connection The connection whose sync status should be checked
* @return If at least one full synchronisation is occurred.
*/
export const useIsConnectionSynced = (connection: YDocMessageTransporter): boolean => {
const [editorEnabled, setEditorEnabled] = useState<boolean>(false)

View file

@ -11,7 +11,7 @@ import type { Extension } from '@codemirror/state'
/**
* Provides an extension that checks when the code mirror, that loads the extension, has its first update.
*
* @return [Extension, boolean] The extension that listens for editor updates and a boolean that defines if the first update already happened
* @return The extension that listens for editor updates and a boolean that defines if the first update already happened
*/
export const useOnFirstEditorUpdateExtension = (): [Extension, boolean] => {
const [firstUpdateHappened, setFirstUpdateHappened] = useState<boolean>(false)

View file

@ -14,7 +14,7 @@ import type { Doc } from 'yjs'
import type { Awareness } from 'y-protocols/awareness'
/**
* Handles the communication with the realtime endpoint of the backend and synchronizes the given y-doc and awareness with other clients..
* Handles the communication with the realtime endpoint of the backend and synchronizes the given y-doc and awareness with other clients.
*/
export class WebsocketConnection extends WebsocketTransporter {
constructor(url: URL, doc: Doc, awareness: Awareness) {

Some files were not shown because too many files have changed in this diff Show more