mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2024-11-25 11:16:31 -05:00
docs: consolidate docs (#2182)
Signed-off-by: Philip Molares <philip.molares@udo.edu>
This commit is contained in:
parent
8d46d7e39e
commit
ecffebc43c
307 changed files with 1474 additions and 487 deletions
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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>>
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
})
|
||||
)
|
||||
})
|
||||
|
|
|
@ -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')) {
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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>(
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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`)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 [
|
||||
{
|
||||
|
|
|
@ -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()) {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -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
|
||||
|
|
91
src/components/common/cache/cache.test.ts
vendored
91
src/components/common/cache/cache.test.ts
vendored
|
@ -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)
|
||||
})
|
||||
})
|
50
src/components/common/cache/cache.ts
vendored
50
src/components/common/cache/cache.ts
vendored
|
@ -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
|
||||
})
|
||||
}
|
||||
}
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 ?? ''}`}>
|
||||
|
|
|
@ -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])
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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())
|
||||
}
|
||||
|
|
|
@ -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'>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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'}>
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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>), [])
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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()
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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'}>
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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()}>
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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}.
|
||||
*/
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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('')
|
||||
|
|
|
@ -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 }) => {
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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')
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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
|
||||
*/
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue