mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-09 13:51:57 -04:00
refactor(frontend): switch to DTOs from @hedgedoc/commons
Co-authored-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
deee8e885f
commit
e411ddf099
121 changed files with 620 additions and 819 deletions
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { NotePermissions } from '@hedgedoc/commons'
|
import type { NotePermissionsDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const mockPermissionChangeApiRoutes = (permission: NotePermissions) => {
|
const mockPermissionChangeApiRoutes = (permission: NotePermissionsDto) => {
|
||||||
cy.intercept('PUT', 'api/private/notes/mock-note/metadata/permissions/groups/_EVERYONE', {
|
cy.intercept('PUT', 'api/private/notes/mock-note/metadata/permissions/groups/_EVERYONE', {
|
||||||
statusCode: 200,
|
statusCode: 200,
|
||||||
body: permission
|
body: permission
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { AuthProvider } from '../../src/api/config/types'
|
import type { ProviderType } from '../../src/api/config/types'
|
||||||
import { AuthProviderType } from '../../src/api/config/types'
|
import { ProviderType } from '../../src/api/config/types'
|
||||||
|
|
||||||
const initLoggedOutTestWithCustomAuthProviders = (cy: Cypress.cy, enabledProviders: AuthProvider[]) => {
|
const initLoggedOutTestWithCustomAuthProviders = (cy: Cypress.cy, enabledProviders: ProviderType[]) => {
|
||||||
cy.logOut()
|
cy.logOut()
|
||||||
cy.loadConfig({
|
cy.loadConfig({
|
||||||
authProviders: enabledProviders
|
authProviders: enabledProviders
|
||||||
|
@ -48,7 +48,7 @@ describe('When logged-out ', () => {
|
||||||
it('sign-in button points to login route: internal', () => {
|
it('sign-in button points to login route: internal', () => {
|
||||||
initLoggedOutTestWithCustomAuthProviders(cy, [
|
initLoggedOutTestWithCustomAuthProviders(cy, [
|
||||||
{
|
{
|
||||||
type: AuthProviderType.LOCAL
|
type: ProviderType.LOCAL
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
cy.getByCypressId('sign-in-button')
|
cy.getByCypressId('sign-in-button')
|
||||||
|
@ -60,7 +60,7 @@ describe('When logged-out ', () => {
|
||||||
it('sign-in button points to login route: ldap', () => {
|
it('sign-in button points to login route: ldap', () => {
|
||||||
initLoggedOutTestWithCustomAuthProviders(cy, [
|
initLoggedOutTestWithCustomAuthProviders(cy, [
|
||||||
{
|
{
|
||||||
type: AuthProviderType.LDAP,
|
type: ProviderType.LDAP,
|
||||||
identifier: 'cy-ldap',
|
identifier: 'cy-ldap',
|
||||||
providerName: 'cy LDAP'
|
providerName: 'cy LDAP'
|
||||||
}
|
}
|
||||||
|
@ -76,7 +76,7 @@ describe('When logged-out ', () => {
|
||||||
it('sign-in button points to auth-provider', () => {
|
it('sign-in button points to auth-provider', () => {
|
||||||
initLoggedOutTestWithCustomAuthProviders(cy, [
|
initLoggedOutTestWithCustomAuthProviders(cy, [
|
||||||
{
|
{
|
||||||
type: AuthProviderType.OIDC,
|
type: ProviderType.OIDC,
|
||||||
identifier: 'github',
|
identifier: 'github',
|
||||||
providerName: 'GitHub',
|
providerName: 'GitHub',
|
||||||
theme: 'github'
|
theme: 'github'
|
||||||
|
@ -94,13 +94,13 @@ describe('When logged-out ', () => {
|
||||||
it('sign-in button points to login route', () => {
|
it('sign-in button points to login route', () => {
|
||||||
initLoggedOutTestWithCustomAuthProviders(cy, [
|
initLoggedOutTestWithCustomAuthProviders(cy, [
|
||||||
{
|
{
|
||||||
type: AuthProviderType.OIDC,
|
type: ProviderType.OIDC,
|
||||||
identifier: 'github',
|
identifier: 'github',
|
||||||
providerName: 'GitHub',
|
providerName: 'GitHub',
|
||||||
theme: 'github'
|
theme: 'github'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AuthProviderType.OIDC,
|
type: ProviderType.OIDC,
|
||||||
identifier: 'gitlab',
|
identifier: 'gitlab',
|
||||||
providerName: 'GitLab',
|
providerName: 'GitLab',
|
||||||
theme: 'gitlab'
|
theme: 'gitlab'
|
||||||
|
@ -117,13 +117,13 @@ describe('When logged-out ', () => {
|
||||||
it('sign-in button points to login route', () => {
|
it('sign-in button points to login route', () => {
|
||||||
initLoggedOutTestWithCustomAuthProviders(cy, [
|
initLoggedOutTestWithCustomAuthProviders(cy, [
|
||||||
{
|
{
|
||||||
type: AuthProviderType.OIDC,
|
type: ProviderType.OIDC,
|
||||||
identifier: 'github',
|
identifier: 'github',
|
||||||
providerName: 'GitHub',
|
providerName: 'GitHub',
|
||||||
theme: 'github'
|
theme: 'github'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AuthProviderType.LOCAL
|
type: ProviderType.LOCAL
|
||||||
}
|
}
|
||||||
])
|
])
|
||||||
cy.getByCypressId('sign-in-button')
|
cy.getByCypressId('sign-in-button')
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { AuthProviderType } from '../../src/api/config/types'
|
import { ProviderType } from '@hedgedoc/commons'
|
||||||
import { HttpMethod } from '../../src/handler-utils/respond-to-matching-request'
|
import { HttpMethod } from '../../src/handler-utils/respond-to-matching-request'
|
||||||
import { IGNORE_MOTD, MOTD_LOCAL_STORAGE_KEY } from '../../src/components/global-dialogs/motd-modal/local-storage-keys'
|
import { IGNORE_MOTD, MOTD_LOCAL_STORAGE_KEY } from '../../src/components/global-dialogs/motd-modal/local-storage-keys'
|
||||||
|
|
||||||
|
@ -22,15 +22,15 @@ export const branding = {
|
||||||
|
|
||||||
export const authProviders = [
|
export const authProviders = [
|
||||||
{
|
{
|
||||||
type: AuthProviderType.LOCAL
|
type: ProviderType.LOCAL
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AuthProviderType.LDAP,
|
type: ProviderType.LDAP,
|
||||||
identifier: 'test-ldap',
|
identifier: 'test-ldap',
|
||||||
providerName: 'Test LDAP'
|
providerName: 'Test LDAP'
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AuthProviderType.OIDC,
|
type: ProviderType.OIDC,
|
||||||
identifier: 'test-oidc',
|
identifier: 'test-oidc',
|
||||||
providerName: 'Test OIDC'
|
providerName: 'Test OIDC'
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { Note } from '../../src/api/notes/types'
|
import type { NoteDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export const testNoteId = 'test'
|
export const testNoteId = 'test'
|
||||||
const mockMetadata = {
|
const mockMetadata = {
|
||||||
|
@ -37,6 +37,6 @@ beforeEach(() => {
|
||||||
content: '',
|
content: '',
|
||||||
metadata: mockMetadata,
|
metadata: mockMetadata,
|
||||||
editedByAtPosition: []
|
editedByAtPosition: []
|
||||||
} as Note)
|
} as NoteDto)
|
||||||
cy.intercept(`api/private/notes/${testNoteId}/metadata`, mockMetadata)
|
cy.intercept(`api/private/notes/${testNoteId}/metadata`, mockMetadata)
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
||||||
import type { Alias, NewAliasDto, PrimaryAliasDto } from './types'
|
import type { AliasDto, AliasCreateDto, AliasUpdateDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adds an alias to an existing note.
|
* Adds an alias to an existing note.
|
||||||
|
@ -16,8 +16,8 @@ import type { Alias, NewAliasDto, PrimaryAliasDto } from './types'
|
||||||
* @return Information about the newly created alias.
|
* @return Information about the newly created alias.
|
||||||
* @throws {Error} when the api request wasn't successful
|
* @throws {Error} when the api request wasn't successful
|
||||||
*/
|
*/
|
||||||
export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise<Alias> => {
|
export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise<AliasDto> => {
|
||||||
const response = await new PostApiRequestBuilder<Alias, NewAliasDto>('alias')
|
const response = await new PostApiRequestBuilder<AliasDto, AliasCreateDto>('alias')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
noteIdOrAlias,
|
noteIdOrAlias,
|
||||||
newAlias
|
newAlias
|
||||||
|
@ -34,8 +34,8 @@ export const addAlias = async (noteIdOrAlias: string, newAlias: string): Promise
|
||||||
* @return The updated information about the alias.
|
* @return The updated information about the alias.
|
||||||
* @throws {Error} when the api request wasn't successfull
|
* @throws {Error} when the api request wasn't successfull
|
||||||
*/
|
*/
|
||||||
export const markAliasAsPrimary = async (alias: string): Promise<Alias> => {
|
export const markAliasAsPrimary = async (alias: string): Promise<AliasDto> => {
|
||||||
const response = await new PutApiRequestBuilder<Alias, PrimaryAliasDto>('alias/' + alias)
|
const response = await new PutApiRequestBuilder<AliasDto, AliasUpdateDto>('alias/' + alias)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
primaryAlias: true
|
primaryAlias: true
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
export interface Alias {
|
|
||||||
name: string
|
|
||||||
primaryAlias: boolean
|
|
||||||
noteId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NewAliasDto {
|
|
||||||
noteIdOrAlias: string
|
|
||||||
newAlias: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PrimaryAliasDto {
|
|
||||||
primaryAlias: boolean
|
|
||||||
}
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import type { AccessToken, AccessTokenWithSecret, CreateAccessTokenDto } from './types'
|
import type { ApiTokenCreateDto, ApiTokenDto, ApiTokenWithSecretDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the access tokens for the current user.
|
* Retrieves the access tokens for the current user.
|
||||||
|
@ -14,8 +14,8 @@ import type { AccessToken, AccessTokenWithSecret, CreateAccessTokenDto } from '.
|
||||||
* @return List of access token metadata.
|
* @return List of access token metadata.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getAccessTokenList = async (): Promise<AccessToken[]> => {
|
export const getAccessTokenList = async (): Promise<ApiTokenDto[]> => {
|
||||||
const response = await new GetApiRequestBuilder<AccessToken[]>('tokens').sendRequest()
|
const response = await new GetApiRequestBuilder<ApiTokenDto[]>('tokens').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ export const getAccessTokenList = async (): Promise<AccessToken[]> => {
|
||||||
* @return The new access token metadata along with its secret.
|
* @return The new access token metadata along with its secret.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const postNewAccessToken = async (label: string, validUntil: number): Promise<AccessTokenWithSecret> => {
|
export const postNewAccessToken = async (label: string, validUntil: Date): Promise<ApiTokenWithSecretDto> => {
|
||||||
const response = await new PostApiRequestBuilder<AccessTokenWithSecret, CreateAccessTokenDto>('tokens')
|
const response = await new PostApiRequestBuilder<ApiTokenWithSecretDto, ApiTokenCreateDto>('tokens')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
label,
|
label,
|
||||||
validUntil
|
validUntil
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import type { LogoutResponseDto, UsernameCheckDto, UsernameCheckResponseDto } from './types'
|
import type { LogoutResponseDto, UsernameCheckDto, UsernameCheckResponseDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests to log out the current user.
|
* Requests to log out the current user.
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import type { LdapLoginResponseDto, LoginDto } from './types'
|
import type { LdapLoginDto, LdapLoginResponseDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests to log in a user via LDAP credentials.
|
* Requests to log in a user via LDAP credentials.
|
||||||
|
@ -19,7 +19,7 @@ export const doLdapLogin = async (
|
||||||
username: string,
|
username: string,
|
||||||
password: string
|
password: string
|
||||||
): Promise<LdapLoginResponseDto> => {
|
): Promise<LdapLoginResponseDto> => {
|
||||||
const response = await new PostApiRequestBuilder<LdapLoginResponseDto, LoginDto>(`auth/ldap/${provider}/login`)
|
const response = await new PostApiRequestBuilder<LdapLoginResponseDto, LdapLoginDto>(`auth/ldap/${provider}/login`)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
username: username,
|
username: username,
|
||||||
password: password
|
password: password
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
||||||
import type { ChangePasswordDto, LoginDto, RegisterDto } from './types'
|
import type { UpdatePasswordDto, LoginDto, RegisterDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests to do a local login with a provided username and password.
|
* Requests to do a local login with a provided username and password.
|
||||||
|
@ -54,7 +54,7 @@ export const doLocalRegister = async (username: string, displayName: string, pas
|
||||||
* @throws {AuthError.LOGIN_DISABLED} when local login is disabled on the backend.
|
* @throws {AuthError.LOGIN_DISABLED} when local login is disabled on the backend.
|
||||||
*/
|
*/
|
||||||
export const doLocalPasswordChange = async (currentPassword: string, newPassword: string): Promise<void> => {
|
export const doLocalPasswordChange = async (currentPassword: string, newPassword: string): Promise<void> => {
|
||||||
await new PutApiRequestBuilder<void, ChangePasswordDto>('auth/local')
|
await new PutApiRequestBuilder<void, UpdatePasswordDto>('auth/local')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
currentPassword,
|
currentPassword,
|
||||||
newPassword
|
newPassword
|
||||||
|
|
|
@ -1,20 +1,20 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { FullUserInfo } from '../users/types'
|
import type { FullUserInfoDto } from '@hedgedoc/commons'
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import type { PendingUserConfirmDto } from './types'
|
import type { PendingUserConfirmationDto } from '@hedgedoc/commons'
|
||||||
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the pending user information.
|
* Fetches the pending user information.
|
||||||
* @returns The pending user information.
|
* @returns The pending user information.
|
||||||
*/
|
*/
|
||||||
export const getPendingUserInfo = async (): Promise<Partial<FullUserInfo>> => {
|
export const getPendingUserInfo = async (): Promise<FullUserInfoDto> => {
|
||||||
const response = await new GetApiRequestBuilder<Partial<FullUserInfo>>('auth/pending-user').sendRequest()
|
const response = await new GetApiRequestBuilder<FullUserInfoDto>('auth/pending-user').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,8 +29,8 @@ export const cancelPendingUser = async (): Promise<void> => {
|
||||||
* Confirms the pending user with updated user information.
|
* Confirms the pending user with updated user information.
|
||||||
* @param updatedUserInfo The updated user information.
|
* @param updatedUserInfo The updated user information.
|
||||||
*/
|
*/
|
||||||
export const confirmPendingUser = async (updatedUserInfo: PendingUserConfirmDto): Promise<void> => {
|
export const confirmPendingUser = async (updatedUserInfo: PendingUserConfirmationDto): Promise<void> => {
|
||||||
await new PutApiRequestBuilder<void, PendingUserConfirmDto>('auth/pending-user')
|
await new PutApiRequestBuilder<void, PendingUserConfirmationDto>('auth/pending-user')
|
||||||
.withJsonBody(updatedUserInfo)
|
.withJsonBody(updatedUserInfo)
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface LoginDto {
|
|
||||||
username: string
|
|
||||||
password: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RegisterDto {
|
|
||||||
username: string
|
|
||||||
password: string
|
|
||||||
displayName: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangePasswordDto {
|
|
||||||
currentPassword: string
|
|
||||||
newPassword: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LogoutResponseDto {
|
|
||||||
redirect: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UsernameCheckDto {
|
|
||||||
username: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UsernameCheckResponseDto {
|
|
||||||
usernameAvailable: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PendingUserConfirmDto {
|
|
||||||
username: string
|
|
||||||
displayName: string
|
|
||||||
profilePicture: string | undefined
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LdapLoginResponseDto {
|
|
||||||
newUser: boolean
|
|
||||||
}
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import type { FrontendConfig } from './types'
|
|
||||||
import { isBuildTime } from '../../utils/test-modes'
|
import { isBuildTime } from '../../utils/test-modes'
|
||||||
|
import type { FrontendConfigDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches the frontend config from the backend.
|
* Fetches the frontend config from the backend.
|
||||||
|
@ -13,10 +13,10 @@ import { isBuildTime } from '../../utils/test-modes'
|
||||||
* @return The frontend config.
|
* @return The frontend config.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getConfig = async (baseUrl?: string): Promise<FrontendConfig | undefined> => {
|
export const getConfig = async (baseUrl?: string): Promise<FrontendConfigDto | undefined> => {
|
||||||
if (isBuildTime) {
|
if (isBuildTime) {
|
||||||
return undefined
|
return undefined
|
||||||
}
|
}
|
||||||
const response = await new GetApiRequestBuilder<FrontendConfig>('config', baseUrl).sendRequest()
|
const response = await new GetApiRequestBuilder<FrontendConfigDto>('config', baseUrl).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,70 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface FrontendConfig {
|
|
||||||
allowRegister: boolean
|
|
||||||
allowProfileEdits: boolean
|
|
||||||
allowChooseUsername: boolean
|
|
||||||
authProviders: AuthProvider[]
|
|
||||||
branding: BrandingConfig
|
|
||||||
guestAccess: GuestAccessLevel
|
|
||||||
useImageProxy: boolean
|
|
||||||
specialUrls: SpecialUrls
|
|
||||||
version: BackendVersion
|
|
||||||
plantumlServer?: string
|
|
||||||
maxDocumentLength: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum GuestAccessLevel {
|
|
||||||
DENY = 'deny',
|
|
||||||
READ = 'read',
|
|
||||||
WRITE = 'write',
|
|
||||||
CREATE = 'create'
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum AuthProviderType {
|
|
||||||
OIDC = 'oidc',
|
|
||||||
LDAP = 'ldap',
|
|
||||||
LOCAL = 'local'
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthProviderTypeWithCustomName = AuthProviderType.LDAP | AuthProviderType.OIDC
|
|
||||||
|
|
||||||
export type AuthProviderTypeWithoutCustomName = AuthProviderType.LOCAL
|
|
||||||
|
|
||||||
export const authProviderTypeOneClick = [AuthProviderType.OIDC]
|
|
||||||
|
|
||||||
export interface AuthProviderWithCustomName {
|
|
||||||
type: AuthProviderTypeWithCustomName
|
|
||||||
identifier: string
|
|
||||||
providerName: string
|
|
||||||
theme?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AuthProviderWithoutCustomName {
|
|
||||||
type: AuthProviderTypeWithoutCustomName
|
|
||||||
}
|
|
||||||
|
|
||||||
export type AuthProvider = AuthProviderWithCustomName | AuthProviderWithoutCustomName
|
|
||||||
|
|
||||||
export interface BrandingConfig {
|
|
||||||
name?: string
|
|
||||||
logo?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface BackendVersion {
|
|
||||||
major: number
|
|
||||||
minor: number
|
|
||||||
patch: number
|
|
||||||
preRelease?: string
|
|
||||||
commit?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface SpecialUrls {
|
|
||||||
privacy?: string
|
|
||||||
termsOfUse?: string
|
|
||||||
imprint?: string
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import type { GroupInfo } from './types'
|
import type { GroupInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about a group with a given name.
|
* Retrieves information about a group with a given name.
|
||||||
|
@ -13,7 +13,7 @@ import type { GroupInfo } from './types'
|
||||||
* @return Information about the group.
|
* @return Information about the group.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getGroup = async (groupName: string): Promise<GroupInfo> => {
|
export const getGroup = async (groupName: string): Promise<GroupInfoDto> => {
|
||||||
const response = await new GetApiRequestBuilder<GroupInfo>('groups/' + groupName).sendRequest()
|
const response = await new GetApiRequestBuilder<GroupInfoDto>('groups/' + groupName).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface GroupInfo {
|
|
||||||
name: string
|
|
||||||
displayName: string
|
|
||||||
special: boolean
|
|
||||||
}
|
|
|
@ -1,13 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import type { MediaUpload } from '../media/types'
|
import type { UpdateUserInfoDto, LoginUserInfoDto, MediaUploadDto } from '@hedgedoc/commons'
|
||||||
import type { ChangeDisplayNameDto, LoginUserInfo } from './types'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns metadata about the currently signed-in user from the API.
|
* Returns metadata about the currently signed-in user from the API.
|
||||||
|
@ -15,8 +14,8 @@ import type { ChangeDisplayNameDto, LoginUserInfo } from './types'
|
||||||
* @return The user metadata.
|
* @return The user metadata.
|
||||||
* @throws {Error} when the user is not signed-in.
|
* @throws {Error} when the user is not signed-in.
|
||||||
*/
|
*/
|
||||||
export const getMe = async (): Promise<LoginUserInfo> => {
|
export const getMe = async (): Promise<LoginUserInfoDto> => {
|
||||||
const response = await new GetApiRequestBuilder<LoginUserInfo>('me').sendRequest()
|
const response = await new GetApiRequestBuilder<LoginUserInfoDto>('me').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -33,12 +32,14 @@ export const deleteUser = async (): Promise<void> => {
|
||||||
* Changes the display name of the current user.
|
* Changes the display name of the current user.
|
||||||
*
|
*
|
||||||
* @param displayName The new display name to set.
|
* @param displayName The new display name to set.
|
||||||
|
* @param email The new email to set.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const updateDisplayName = async (displayName: string): Promise<void> => {
|
export const updateUser = async (displayName: string | null, email: string | null): Promise<void> => {
|
||||||
await new PostApiRequestBuilder<void, ChangeDisplayNameDto>('me/profile')
|
await new PostApiRequestBuilder<void, UpdateUserInfoDto>('me/profile')
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
displayName
|
displayName,
|
||||||
|
email
|
||||||
})
|
})
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
}
|
}
|
||||||
|
@ -49,7 +50,7 @@ export const updateDisplayName = async (displayName: string): Promise<void> => {
|
||||||
* @return List of media object information.
|
* @return List of media object information.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getMyMedia = async (): Promise<MediaUpload[]> => {
|
export const getMyMedia = async (): Promise<MediaUploadDto[]> => {
|
||||||
const response = await new GetApiRequestBuilder<MediaUpload[]>('me/media').sendRequest()
|
const response = await new GetApiRequestBuilder<MediaUploadDto[]>('me/media').sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import type { UserInfo } from '../users/types'
|
|
||||||
|
|
||||||
export interface LoginUserInfo extends UserInfo {
|
|
||||||
authProvider: string
|
|
||||||
email: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ChangeDisplayNameDto {
|
|
||||||
displayName: string
|
|
||||||
}
|
|
|
@ -1,11 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import type { ImageProxyRequestDto, ImageProxyResponse, MediaUpload } from './types'
|
import type { ImageProxyRequestDto, ImageProxyResponse } from './types'
|
||||||
|
import type { MediaUploadDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests an image-proxy URL from the backend for a given image URL.
|
* Requests an image-proxy URL from the backend for a given image URL.
|
||||||
|
@ -31,10 +32,10 @@ export const getProxiedUrl = async (imageUrl: string): Promise<ImageProxyRespons
|
||||||
* @return The URL of the uploaded media object.
|
* @return The URL of the uploaded media object.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const uploadFile = async (noteIdOrAlias: string, media: File): Promise<MediaUpload> => {
|
export const uploadFile = async (noteIdOrAlias: string, media: File): Promise<MediaUploadDto> => {
|
||||||
const postData = new FormData()
|
const postData = new FormData()
|
||||||
postData.append('file', media)
|
postData.append('file', media)
|
||||||
const response = await new PostApiRequestBuilder<MediaUpload, void>('media')
|
const response = await new PostApiRequestBuilder<MediaUploadDto, void>('media')
|
||||||
.withHeader('HedgeDoc-Note', noteIdOrAlias)
|
.withHeader('HedgeDoc-Note', noteIdOrAlias)
|
||||||
.withBody(postData)
|
.withBody(postData)
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
|
|
@ -1,16 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
export interface MediaUpload {
|
|
||||||
uuid: string
|
|
||||||
fileName: string
|
|
||||||
noteId: string | null
|
|
||||||
createdAt: string
|
|
||||||
username: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ImageProxyResponse {
|
export interface ImageProxyResponse {
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
import { PostApiRequestBuilder } from '../common/api-request-builder/post-api-request-builder'
|
||||||
import type { MediaUpload } from '../media/types'
|
import type { MediaUploadDto, NoteDto, NoteMetadataDto } from '@hedgedoc/commons'
|
||||||
import type { Note, NoteDeletionOptions, NoteMetadata } from './types'
|
import type { NoteMediaDeletionDto } from '@hedgedoc/commons/dist/esm'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the content and metadata about the specified note.
|
* Retrieves the content and metadata about the specified note.
|
||||||
|
@ -16,8 +16,8 @@ import type { Note, NoteDeletionOptions, NoteMetadata } from './types'
|
||||||
* @return Content and metadata of the specified note.
|
* @return Content and metadata of the specified note.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getNote = async (noteIdOrAlias: string, baseUrl?: string): Promise<Note> => {
|
export const getNote = async (noteIdOrAlias: string, baseUrl?: string): Promise<NoteDto> => {
|
||||||
const response = await new GetApiRequestBuilder<Note>('notes/' + noteIdOrAlias, baseUrl).sendRequest()
|
const response = await new GetApiRequestBuilder<NoteDto>('notes/' + noteIdOrAlias, baseUrl).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,8 +27,8 @@ export const getNote = async (noteIdOrAlias: string, baseUrl?: string): Promise<
|
||||||
* @param noteIdOrAlias The id or alias of the note.
|
* @param noteIdOrAlias The id or alias of the note.
|
||||||
* @return Metadata of the specified note.
|
* @return Metadata of the specified note.
|
||||||
*/
|
*/
|
||||||
export const getNoteMetadata = async (noteIdOrAlias: string): Promise<NoteMetadata> => {
|
export const getNoteMetadata = async (noteIdOrAlias: string): Promise<NoteMetadataDto> => {
|
||||||
const response = await new GetApiRequestBuilder<NoteMetadata>(`notes/${noteIdOrAlias}/metadata`).sendRequest()
|
const response = await new GetApiRequestBuilder<NoteMetadataDto>(`notes/${noteIdOrAlias}/metadata`).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -39,8 +39,8 @@ export const getNoteMetadata = async (noteIdOrAlias: string): Promise<NoteMetada
|
||||||
* @return List of media object metadata associated with specified note.
|
* @return List of media object metadata associated with specified note.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUpload[]> => {
|
export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUploadDto[]> => {
|
||||||
const response = await new GetApiRequestBuilder<MediaUpload[]>(`notes/${noteIdOrAlias}/media`).sendRequest()
|
const response = await new GetApiRequestBuilder<MediaUploadDto[]>(`notes/${noteIdOrAlias}/media`).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,8 +51,8 @@ export const getMediaForNote = async (noteIdOrAlias: string): Promise<MediaUploa
|
||||||
* @return Content and metadata of the new note.
|
* @return Content and metadata of the new note.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const createNote = async (markdown: string): Promise<Note> => {
|
export const createNote = async (markdown: string): Promise<NoteDto> => {
|
||||||
const response = await new PostApiRequestBuilder<Note, void>('notes')
|
const response = await new PostApiRequestBuilder<NoteDto, void>('notes')
|
||||||
.withHeader('Content-Type', 'text/markdown')
|
.withHeader('Content-Type', 'text/markdown')
|
||||||
.withBody(markdown)
|
.withBody(markdown)
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -67,8 +67,8 @@ export const createNote = async (markdown: string): Promise<Note> => {
|
||||||
* @return Content and metadata of the new note.
|
* @return Content and metadata of the new note.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias: string): Promise<Note> => {
|
export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias: string): Promise<NoteDto> => {
|
||||||
const response = await new PostApiRequestBuilder<Note, void>('notes/' + primaryAlias)
|
const response = await new PostApiRequestBuilder<NoteDto, void>('notes/' + primaryAlias)
|
||||||
.withHeader('Content-Type', 'text/markdown')
|
.withHeader('Content-Type', 'text/markdown')
|
||||||
.withBody(markdown)
|
.withBody(markdown)
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
|
@ -83,7 +83,7 @@ export const createNoteWithPrimaryAlias = async (markdown: string, primaryAlias:
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const deleteNote = async (noteIdOrAlias: string, keepMedia: boolean): Promise<void> => {
|
export const deleteNote = async (noteIdOrAlias: string, keepMedia: boolean): Promise<void> => {
|
||||||
await new DeleteApiRequestBuilder<void, NoteDeletionOptions>('notes/' + noteIdOrAlias)
|
await new DeleteApiRequestBuilder<void, NoteMediaDeletionDto>('notes/' + noteIdOrAlias)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
keepMedia
|
keepMedia
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,41 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import type { Alias } from '../alias/types'
|
|
||||||
import type { NotePermissions } from '@hedgedoc/commons'
|
|
||||||
|
|
||||||
export interface Note {
|
|
||||||
content: string
|
|
||||||
metadata: NoteMetadata
|
|
||||||
editedByAtPosition: NoteEdit[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NoteMetadata {
|
|
||||||
id: string
|
|
||||||
aliases: Alias[]
|
|
||||||
primaryAddress: string
|
|
||||||
title: string
|
|
||||||
description: string
|
|
||||||
tags: string[]
|
|
||||||
updatedAt: string
|
|
||||||
updateUsername: string | null
|
|
||||||
viewCount: number
|
|
||||||
createdAt: string
|
|
||||||
editedBy: string[]
|
|
||||||
permissions: NotePermissions
|
|
||||||
version: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NoteEdit {
|
|
||||||
username: string | null
|
|
||||||
startPos: number
|
|
||||||
endPos: number
|
|
||||||
createdAt: string
|
|
||||||
updatedAt: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface NoteDeletionOptions {
|
|
||||||
keepMedia: boolean
|
|
||||||
}
|
|
|
@ -1,27 +1,31 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
import { PutApiRequestBuilder } from '../common/api-request-builder/put-api-request-builder'
|
||||||
import type { OwnerChangeDto, PermissionSetDto } from './types'
|
import type {
|
||||||
import type { NotePermissions } from '@hedgedoc/commons'
|
ChangeNoteOwnerDto,
|
||||||
|
NoteGroupPermissionUpdateDto,
|
||||||
|
NotePermissionsDto,
|
||||||
|
NoteUserPermissionUpdateDto
|
||||||
|
} from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the owner of a note.
|
* Sets the owner of a note.
|
||||||
*
|
*
|
||||||
* @param noteId The id of the note.
|
* @param noteId The id of the note.
|
||||||
* @param newOwner The username of the new owner.
|
* @param newOwner The username of the new owner.
|
||||||
* @return The updated {@link NotePermissions}.
|
* @return The updated {@link NotePermissionsDto}.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const setNoteOwner = async (noteId: string, newOwner: string): Promise<NotePermissions> => {
|
export const setNoteOwner = async (noteId: string, newOwner: string): Promise<NotePermissionsDto> => {
|
||||||
const response = await new PutApiRequestBuilder<NotePermissions, OwnerChangeDto>(
|
const response = await new PutApiRequestBuilder<NotePermissionsDto, ChangeNoteOwnerDto>(
|
||||||
`notes/${noteId}/metadata/permissions/owner`
|
`notes/${noteId}/metadata/permissions/owner`
|
||||||
)
|
)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
newOwner
|
owner: newOwner
|
||||||
})
|
})
|
||||||
.sendRequest()
|
.sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
|
@ -33,15 +37,15 @@ export const setNoteOwner = async (noteId: string, newOwner: string): Promise<No
|
||||||
* @param noteId The id of the note.
|
* @param noteId The id of the note.
|
||||||
* @param username The username of the user to set the permission for.
|
* @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.
|
* @param canEdit true if the user should be able to update the note, false otherwise.
|
||||||
* @return The updated {@link NotePermissions}.
|
* @return The updated {@link NotePermissionsDto}.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const setUserPermission = async (
|
export const setUserPermission = async (
|
||||||
noteId: string,
|
noteId: string,
|
||||||
username: string,
|
username: string,
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
): Promise<NotePermissions> => {
|
): Promise<NotePermissionsDto> => {
|
||||||
const response = await new PutApiRequestBuilder<NotePermissions, PermissionSetDto>(
|
const response = await new PutApiRequestBuilder<NotePermissionsDto, Pick<NoteUserPermissionUpdateDto, 'canEdit'>>(
|
||||||
`notes/${noteId}/metadata/permissions/users/${username}`
|
`notes/${noteId}/metadata/permissions/users/${username}`
|
||||||
)
|
)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
|
@ -57,15 +61,15 @@ export const setUserPermission = async (
|
||||||
* @param noteId The id of the note.
|
* @param noteId The id of the note.
|
||||||
* @param groupName The name of the group to set the permission for.
|
* @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.
|
* @param canEdit true if the group should be able to update the note, false otherwise.
|
||||||
* @return The updated {@link NotePermissions}.
|
* @return The updated {@link NotePermissionsDto}.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const setGroupPermission = async (
|
export const setGroupPermission = async (
|
||||||
noteId: string,
|
noteId: string,
|
||||||
groupName: string,
|
groupName: string,
|
||||||
canEdit: boolean
|
canEdit: boolean
|
||||||
): Promise<NotePermissions> => {
|
): Promise<NotePermissionsDto> => {
|
||||||
const response = await new PutApiRequestBuilder<NotePermissions, PermissionSetDto>(
|
const response = await new PutApiRequestBuilder<NotePermissionsDto, Pick<NoteGroupPermissionUpdateDto, 'canEdit'>>(
|
||||||
`notes/${noteId}/metadata/permissions/groups/${groupName}`
|
`notes/${noteId}/metadata/permissions/groups/${groupName}`
|
||||||
)
|
)
|
||||||
.withJsonBody({
|
.withJsonBody({
|
||||||
|
@ -80,11 +84,11 @@ export const setGroupPermission = async (
|
||||||
*
|
*
|
||||||
* @param noteId The id of the note.
|
* @param noteId The id of the note.
|
||||||
* @param username The name of the user to remove the permission of.
|
* @param username The name of the user to remove the permission of.
|
||||||
* @return The updated {@link NotePermissions}.
|
* @return The updated {@link NotePermissionsDto}.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const removeUserPermission = async (noteId: string, username: string): Promise<NotePermissions> => {
|
export const removeUserPermission = async (noteId: string, username: string): Promise<NotePermissionsDto> => {
|
||||||
const response = await new DeleteApiRequestBuilder<NotePermissions>(
|
const response = await new DeleteApiRequestBuilder<NotePermissionsDto>(
|
||||||
`notes/${noteId}/metadata/permissions/users/${username}`
|
`notes/${noteId}/metadata/permissions/users/${username}`
|
||||||
).sendRequest()
|
).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
|
@ -95,11 +99,11 @@ export const removeUserPermission = async (noteId: string, username: string): Pr
|
||||||
*
|
*
|
||||||
* @param noteId The id of the note.
|
* @param noteId The id of the note.
|
||||||
* @param groupName The name of the group to remove the permission of.
|
* @param groupName The name of the group to remove the permission of.
|
||||||
* @return The updated {@link NotePermissions}.
|
* @return The updated {@link NotePermissionsDto}.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const removeGroupPermission = async (noteId: string, groupName: string): Promise<NotePermissions> => {
|
export const removeGroupPermission = async (noteId: string, groupName: string): Promise<NotePermissionsDto> => {
|
||||||
const response = await new DeleteApiRequestBuilder<NotePermissions>(
|
const response = await new DeleteApiRequestBuilder<NotePermissionsDto>(
|
||||||
`notes/${noteId}/metadata/permissions/groups/${groupName}`
|
`notes/${noteId}/metadata/permissions/groups/${groupName}`
|
||||||
).sendRequest()
|
).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
export interface OwnerChangeDto {
|
|
||||||
newOwner: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PermissionSetDto {
|
|
||||||
canEdit: boolean
|
|
||||||
}
|
|
|
@ -1,11 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
import { DeleteApiRequestBuilder } from '../common/api-request-builder/delete-api-request-builder'
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import type { RevisionDetails, RevisionMetadata } from './types'
|
import type { RevisionDto, RevisionMetadataDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a note revision while using a cache for often retrieved revisions.
|
* Retrieves a note revision while using a cache for often retrieved revisions.
|
||||||
|
@ -15,10 +15,8 @@ import type { RevisionDetails, RevisionMetadata } from './types'
|
||||||
* @return The revision.
|
* @return The revision.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getRevision = async (noteId: string, revisionId: number): Promise<RevisionDetails> => {
|
export const getRevision = async (noteId: string, revisionId: number): Promise<RevisionDto> => {
|
||||||
const response = await new GetApiRequestBuilder<RevisionDetails>(
|
const response = await new GetApiRequestBuilder<RevisionDto>(`notes/${noteId}/revisions/${revisionId}`).sendRequest()
|
||||||
`notes/${noteId}/revisions/${revisionId}`
|
|
||||||
).sendRequest()
|
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -29,8 +27,8 @@ export const getRevision = async (noteId: string, revisionId: number): Promise<R
|
||||||
* @return A list of revision ids.
|
* @return A list of revision ids.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getAllRevisions = async (noteId: string): Promise<RevisionMetadata[]> => {
|
export const getAllRevisions = async (noteId: string): Promise<RevisionMetadataDto[]> => {
|
||||||
const response = await new GetApiRequestBuilder<RevisionMetadata[]>(`notes/${noteId}/revisions`).sendRequest()
|
const response = await new GetApiRequestBuilder<RevisionMetadataDto[]>(`notes/${noteId}/revisions`).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import type { NoteEdit } from '../notes/types'
|
|
||||||
|
|
||||||
export interface RevisionDetails extends RevisionMetadata {
|
|
||||||
content: string
|
|
||||||
patch: string
|
|
||||||
edits: NoteEdit[]
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface RevisionMetadata {
|
|
||||||
id: number
|
|
||||||
createdAt: string
|
|
||||||
length: number
|
|
||||||
authorUsernames: string[]
|
|
||||||
anonymousAuthorCount: number
|
|
||||||
title: string
|
|
||||||
tags: string[]
|
|
||||||
description: string
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface AccessToken {
|
|
||||||
label: string
|
|
||||||
validUntil: string
|
|
||||||
keyId: string
|
|
||||||
createdAt: string
|
|
||||||
lastUsedAt: string | null
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AccessTokenWithSecret extends AccessToken {
|
|
||||||
secret: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreateAccessTokenDto {
|
|
||||||
label: string
|
|
||||||
validUntil: number
|
|
||||||
}
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||||
import type { UserInfo } from './types'
|
import type { UserInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about a specific user while using a cache to avoid many requests for the same username.
|
* Retrieves information about a specific user while using a cache to avoid many requests for the same username.
|
||||||
|
@ -13,7 +13,7 @@ import type { UserInfo } from './types'
|
||||||
* @return Metadata about the requested user.
|
* @return Metadata about the requested user.
|
||||||
* @throws {Error} when the api request wasn't successful.
|
* @throws {Error} when the api request wasn't successful.
|
||||||
*/
|
*/
|
||||||
export const getUserInfo = async (username: string): Promise<UserInfo> => {
|
export const getUserInfo = async (username: string): Promise<UserInfoDto> => {
|
||||||
const response = await new GetApiRequestBuilder<UserInfo>(`users/profile/${username}`).sendRequest()
|
const response = await new GetApiRequestBuilder<UserInfoDto>(`users/profile/${username}`).sendRequest()
|
||||||
return response.asParsedJsonObject()
|
return response.asParsedJsonObject()
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,15 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
|
|
||||||
export interface UserInfo {
|
|
||||||
username: string
|
|
||||||
displayName: string
|
|
||||||
photoUrl?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface FullUserInfo extends UserInfo {
|
|
||||||
email: string
|
|
||||||
}
|
|
|
@ -1,16 +1,15 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { mockI18n } from '../../../../../test-utils/mock-i18n'
|
import { mockI18n } from '../../../../../test-utils/mock-i18n'
|
||||||
import { EditorAppBar } from './editor-app-bar'
|
import { EditorAppBar } from './editor-app-bar'
|
||||||
import type { NoteGroupPermissionEntry, NoteUserPermissionEntry } from '@hedgedoc/commons'
|
import type { LoginUserInfoDto, NoteGroupPermissionEntryDto, NoteUserPermissionEntryDto } from '@hedgedoc/commons'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import type { PropsWithChildren } from 'react'
|
import type { PropsWithChildren } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { mockAppState } from '../../../../../test-utils/mock-app-state'
|
import { mockAppState } from '../../../../../test-utils/mock-app-state'
|
||||||
import type { LoginUserInfo } from '../../../../../api/me/types'
|
|
||||||
|
|
||||||
jest.mock('../../../../../components/layout/app-bar/base-app-bar', () => ({
|
jest.mock('../../../../../components/layout/app-bar/base-app-bar', () => ({
|
||||||
__esModule: true,
|
__esModule: true,
|
||||||
|
@ -34,13 +33,13 @@ const mockedCommonAppState = {
|
||||||
groupName: '_EVERYONE',
|
groupName: '_EVERYONE',
|
||||||
canEdit: false
|
canEdit: false
|
||||||
}
|
}
|
||||||
] as NoteGroupPermissionEntry[],
|
] as NoteGroupPermissionEntryDto[],
|
||||||
sharedToUsers: [] as NoteUserPermissionEntry[]
|
sharedToUsers: [] as NoteUserPermissionEntryDto[]
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
user: {
|
user: {
|
||||||
username: 'test'
|
username: 'test'
|
||||||
} as LoginUserInfo
|
} as LoginUserInfoDto
|
||||||
}
|
}
|
||||||
|
|
||||||
describe('app bar', () => {
|
describe('app bar', () => {
|
||||||
|
|
|
@ -1,3 +1,9 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -5,7 +11,7 @@
|
||||||
|
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { AuthProviderType } from '../../../api/config/types'
|
import { ProviderType } from '@hedgedoc/commons'
|
||||||
import { Redirect } from '../../../components/common/redirect'
|
import { Redirect } from '../../../components/common/redirect'
|
||||||
import { LandingLayout } from '../../../components/landing-layout/landing-layout'
|
import { LandingLayout } from '../../../components/landing-layout/landing-layout'
|
||||||
import { ProfileAccessTokens } from '../../../components/profile-page/access-tokens/profile-access-tokens'
|
import { ProfileAccessTokens } from '../../../components/profile-page/access-tokens/profile-access-tokens'
|
||||||
|
@ -34,7 +40,7 @@ const ProfilePage: NextPage = () => {
|
||||||
<Row className='h-100 flex justify-content-center'>
|
<Row className='h-100 flex justify-content-center'>
|
||||||
<Col lg={6}>
|
<Col lg={6}>
|
||||||
<ProfileDisplayName />
|
<ProfileDisplayName />
|
||||||
{userProvider === (AuthProviderType.LOCAL as string) && <ProfileChangePassword />}
|
{userProvider === ProviderType.LOCAL && <ProfileChangePassword />}
|
||||||
<ProfileAccessTokens />
|
<ProfileAccessTokens />
|
||||||
<ProfileAccountManagement />
|
<ProfileAccountManagement />
|
||||||
</Col>
|
</Col>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -20,7 +20,7 @@ describe('Copy to clipboard button', () => {
|
||||||
beforeAll(async () => {
|
beforeAll(async () => {
|
||||||
await mockI18n()
|
await mockI18n()
|
||||||
originalClipboard = window.navigator.clipboard
|
originalClipboard = window.navigator.clipboard
|
||||||
jest.spyOn(uuidModule, 'v4').mockReturnValue(uuidMock)
|
jest.spyOn(uuidModule, 'v4').mockReturnValue(Buffer.from(uuidMock))
|
||||||
})
|
})
|
||||||
|
|
||||||
afterAll(() => {
|
afterAll(() => {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { FrontendConfig } from '../../../api/config/types'
|
import type { FrontendConfigDto } from '@hedgedoc/commons'
|
||||||
import * as UseFrontendConfigMock from '../frontend-config-context/use-frontend-config'
|
import * as UseFrontendConfigMock from '../frontend-config-context/use-frontend-config'
|
||||||
import { CustomBranding } from './custom-branding'
|
import { CustomBranding } from './custom-branding'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
|
@ -12,10 +12,10 @@ import { Mock } from 'ts-mockery'
|
||||||
jest.mock('../frontend-config-context/use-frontend-config')
|
jest.mock('../frontend-config-context/use-frontend-config')
|
||||||
|
|
||||||
describe('custom branding', () => {
|
describe('custom branding', () => {
|
||||||
const mockFrontendConfigHook = (logo?: string, name?: string) => {
|
const mockFrontendConfigHook = (logo: string | null = null, name: string | null = null) => {
|
||||||
jest
|
jest
|
||||||
.spyOn(UseFrontendConfigMock, 'useFrontendConfig')
|
.spyOn(UseFrontendConfigMock, 'useFrontendConfig')
|
||||||
.mockReturnValue(Mock.of<FrontendConfig>({ branding: { logo, name } }))
|
.mockReturnValue(Mock.of<FrontendConfigDto>({ branding: { logo, name } }))
|
||||||
}
|
}
|
||||||
|
|
||||||
it("doesn't show anything if no branding is defined", () => {
|
it("doesn't show anything if no branding is defined", () => {
|
||||||
|
@ -32,7 +32,7 @@ describe('custom branding', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows an text if branding text is defined', () => {
|
it('shows an text if branding text is defined', () => {
|
||||||
mockFrontendConfigHook(undefined, 'mockedBranding')
|
mockFrontendConfigHook(null, 'mockedBranding')
|
||||||
const view = render(<CustomBranding inline={inline} />)
|
const view = render(<CustomBranding inline={inline} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -36,7 +36,12 @@ export const CustomBranding: React.FC<BrandingProps> = ({ inline = false }) => {
|
||||||
} else if (branding.logo) {
|
} else if (branding.logo) {
|
||||||
return (
|
return (
|
||||||
/* eslint-disable-next-line @next/next/no-img-element */
|
/* eslint-disable-next-line @next/next/no-img-element */
|
||||||
<img src={branding.logo} alt={branding.name} title={branding.name} className={className} />
|
<img
|
||||||
|
src={branding.logo}
|
||||||
|
alt={branding.name !== null ? branding.name : undefined}
|
||||||
|
title={branding.name !== null ? branding.name : undefined}
|
||||||
|
className={className}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return <span className={className}>{branding.name}</span>
|
return <span className={className}>{branding.name}</span>
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { BrandingConfig } from '../../../api/config/types'
|
import type { BrandingDto } from '@hedgedoc/commons'
|
||||||
import { useFrontendConfig } from '../frontend-config-context/use-frontend-config'
|
import { useFrontendConfig } from '../frontend-config-context/use-frontend-config'
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
|
@ -12,10 +12,10 @@ import { useMemo } from 'react'
|
||||||
*
|
*
|
||||||
* @return the branding configuration or null if no branding has been configured
|
* @return the branding configuration or null if no branding has been configured
|
||||||
*/
|
*/
|
||||||
export const useBrandingDetails = (): null | BrandingConfig => {
|
export const useBrandingDetails = (): null | BrandingDto => {
|
||||||
const branding = useFrontendConfig().branding
|
const branding = useFrontendConfig().branding
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
return !branding.name && !branding.logo ? null : branding
|
return branding.name === null && branding.logo === null ? null : branding
|
||||||
}, [branding])
|
}, [branding])
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -18,7 +18,7 @@ export enum ProfilePictureChoice {
|
||||||
export interface ProfilePictureSelectFieldProps extends CommonFieldProps<ProfilePictureChoice> {
|
export interface ProfilePictureSelectFieldProps extends CommonFieldProps<ProfilePictureChoice> {
|
||||||
onChange: (choice: ProfilePictureChoice) => void
|
onChange: (choice: ProfilePictureChoice) => void
|
||||||
value: ProfilePictureChoice
|
value: ProfilePictureChoice
|
||||||
pictureUrl?: string
|
photoUrl: string | null
|
||||||
username: string
|
username: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,11 +31,15 @@ export interface ProfilePictureSelectFieldProps extends CommonFieldProps<Profile
|
||||||
*/
|
*/
|
||||||
export const ProfilePictureSelectField: React.FC<ProfilePictureSelectFieldProps> = ({
|
export const ProfilePictureSelectField: React.FC<ProfilePictureSelectFieldProps> = ({
|
||||||
onChange,
|
onChange,
|
||||||
pictureUrl,
|
photoUrl,
|
||||||
username,
|
username,
|
||||||
value
|
value
|
||||||
}) => {
|
}) => {
|
||||||
const fallbackUrl = useAvatarUrl(undefined, username)
|
const fallbackUrl = useAvatarUrl({
|
||||||
|
username,
|
||||||
|
photoUrl,
|
||||||
|
displayName: username
|
||||||
|
})
|
||||||
const profileEditsAllowed = useFrontendConfig().allowProfileEdits
|
const profileEditsAllowed = useFrontendConfig().allowProfileEdits
|
||||||
const onSetProviderPicture = useCallback(() => {
|
const onSetProviderPicture = useCallback(() => {
|
||||||
if (value !== ProfilePictureChoice.PROVIDER) {
|
if (value !== ProfilePictureChoice.PROVIDER) {
|
||||||
|
@ -57,7 +61,7 @@ export const ProfilePictureSelectField: React.FC<ProfilePictureSelectFieldProps>
|
||||||
<Form.Label>
|
<Form.Label>
|
||||||
<Trans i18nKey='profile.selectProfilePicture.title' />
|
<Trans i18nKey='profile.selectProfilePicture.title' />
|
||||||
</Form.Label>
|
</Form.Label>
|
||||||
{pictureUrl && (
|
{photoUrl && (
|
||||||
<Form.Check className={'d-flex gap-2 align-items-center mb-3'} type='radio'>
|
<Form.Check className={'d-flex gap-2 align-items-center mb-3'} type='radio'>
|
||||||
<Form.Check.Input
|
<Form.Check.Input
|
||||||
type={'radio'}
|
type={'radio'}
|
||||||
|
@ -66,7 +70,7 @@ export const ProfilePictureSelectField: React.FC<ProfilePictureSelectFieldProps>
|
||||||
/>
|
/>
|
||||||
<Form.Check.Label>
|
<Form.Check.Label>
|
||||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||||
<img src={pictureUrl} alt={'Profile picture provided by the identity provider'} height={48} width={48} />
|
<img src={photoUrl} alt={'Profile picture provided by the identity provider'} height={48} width={48} />
|
||||||
</Form.Check.Label>
|
</Form.Check.Label>
|
||||||
</Form.Check>
|
</Form.Check>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { FrontendConfig } from '../../../api/config/types'
|
import type { FrontendConfigDto } from '@hedgedoc/commons'
|
||||||
import { createContext } from 'react'
|
import { createContext } from 'react'
|
||||||
|
|
||||||
export const frontendConfigContext = createContext<FrontendConfig | undefined>(undefined)
|
export const frontendConfigContext = createContext<FrontendConfigDto | undefined>(undefined)
|
||||||
|
|
|
@ -1,16 +1,22 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
'use client'
|
'use client'
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { FrontendConfig } from '../../../api/config/types'
|
import type { FrontendConfigDto } from '@hedgedoc/commons'
|
||||||
import { frontendConfigContext } from './context'
|
import { frontendConfigContext } from './context'
|
||||||
import type { PropsWithChildren } from 'react'
|
import type { PropsWithChildren } from 'react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
|
||||||
interface FrontendConfigContextProviderProps extends PropsWithChildren {
|
interface FrontendConfigContextProviderProps extends PropsWithChildren {
|
||||||
config?: FrontendConfig
|
config?: FrontendConfigDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { FrontendConfig } from '../../../api/config/types'
|
import type { FrontendConfigDto } from '@hedgedoc/commons'
|
||||||
import { frontendConfigContext } from './context'
|
import { frontendConfigContext } from './context'
|
||||||
import { Optional } from '@mrdrogdrog/optional'
|
import { Optional } from '@mrdrogdrog/optional'
|
||||||
import { useContext } from 'react'
|
import { useContext } from 'react'
|
||||||
|
@ -11,7 +11,7 @@ import { useContext } from 'react'
|
||||||
/**
|
/**
|
||||||
* Retrieves the current frontend config from the next react context.
|
* Retrieves the current frontend config from the next react context.
|
||||||
*/
|
*/
|
||||||
export const useFrontendConfig = (): FrontendConfig => {
|
export const useFrontendConfig = (): FrontendConfigDto => {
|
||||||
return Optional.ofNullable(useContext(frontendConfigContext)).orElseThrow(
|
return Optional.ofNullable(useContext(frontendConfigContext)).orElseThrow(
|
||||||
() => new Error('No frontend config context found. Did you forget to use the provider component?')
|
() => new Error('No frontend config context found. Did you forget to use the provider component?')
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -12,7 +12,7 @@ import React, { useCallback } from 'react'
|
||||||
import { FileEarmarkPlus as IconPlus } from 'react-bootstrap-icons'
|
import { FileEarmarkPlus as IconPlus } from 'react-bootstrap-icons'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
import { useFrontendConfig } from '../frontend-config-context/use-frontend-config'
|
import { useFrontendConfig } from '../frontend-config-context/use-frontend-config'
|
||||||
import { GuestAccessLevel } from '../../../api/config/types'
|
import { GuestAccess } from '@hedgedoc/commons'
|
||||||
import { useIsLoggedIn } from '../../../hooks/common/use-is-logged-in'
|
import { useIsLoggedIn } from '../../../hooks/common/use-is-logged-in'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -34,7 +34,7 @@ export const NewNoteButton: React.FC = () => {
|
||||||
})
|
})
|
||||||
}, [router, showErrorNotification])
|
}, [router, showErrorNotification])
|
||||||
|
|
||||||
if (!isLoggedIn && guestAccessLevel !== GuestAccessLevel.CREATE) {
|
if (!isLoggedIn && guestAccessLevel !== GuestAccess.CREATE) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import * as createNoteWithPrimaryAliasModule from '../../../api/notes'
|
import * as createNoteWithPrimaryAliasModule from '../../../api/notes'
|
||||||
import type { Note, NoteMetadata } from '../../../api/notes/types'
|
|
||||||
import { mockI18n } from '../../../test-utils/mock-i18n'
|
import { mockI18n } from '../../../test-utils/mock-i18n'
|
||||||
import { CreateNonExistingNoteHint } from './create-non-existing-note-hint'
|
import { CreateNonExistingNoteHint } from './create-non-existing-note-hint'
|
||||||
|
import type { NoteDto, NoteMetadataDto } from '@hedgedoc/commons'
|
||||||
import { waitForOtherPromisesToFinish } from '@hedgedoc/commons'
|
import { waitForOtherPromisesToFinish } from '@hedgedoc/commons'
|
||||||
import { act, render, screen, waitFor } from '@testing-library/react'
|
import { act, render, screen, waitFor } from '@testing-library/react'
|
||||||
import { Mock } from 'ts-mockery'
|
import { Mock } from 'ts-mockery'
|
||||||
|
@ -20,20 +20,20 @@ describe('create non existing note hint', () => {
|
||||||
const mockCreateNoteWithPrimaryAlias = () => {
|
const mockCreateNoteWithPrimaryAlias = () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(createNoteWithPrimaryAliasModule, 'createNoteWithPrimaryAlias')
|
.spyOn(createNoteWithPrimaryAliasModule, 'createNoteWithPrimaryAlias')
|
||||||
.mockImplementation(async (markdown, primaryAlias): Promise<Note> => {
|
.mockImplementation(async (markdown, primaryAlias): Promise<NoteDto> => {
|
||||||
expect(markdown).toBe('')
|
expect(markdown).toBe('')
|
||||||
expect(primaryAlias).toBe(mockedNoteId)
|
expect(primaryAlias).toBe(mockedNoteId)
|
||||||
const metadata: NoteMetadata = Mock.of<NoteMetadata>({ primaryAddress: 'mockedPrimaryAlias' })
|
const metadata: NoteMetadataDto = Mock.of<NoteMetadataDto>({ primaryAddress: 'mockedPrimaryAlias' })
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||||
await waitForOtherPromisesToFinish()
|
await waitForOtherPromisesToFinish()
|
||||||
return Mock.of<Note>({ metadata })
|
return Mock.of<NoteDto>({ metadata })
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockFailingCreateNoteWithPrimaryAlias = () => {
|
const mockFailingCreateNoteWithPrimaryAlias = () => {
|
||||||
jest
|
jest
|
||||||
.spyOn(createNoteWithPrimaryAliasModule, 'createNoteWithPrimaryAlias')
|
.spyOn(createNoteWithPrimaryAliasModule, 'createNoteWithPrimaryAlias')
|
||||||
.mockImplementation(async (markdown, primaryAlias): Promise<Note> => {
|
.mockImplementation(async (markdown, primaryAlias): Promise<NoteDto> => {
|
||||||
expect(markdown).toBe('')
|
expect(markdown).toBe('')
|
||||||
expect(primaryAlias).toBe(mockedNoteId)
|
expect(primaryAlias).toBe(mockedNoteId)
|
||||||
await waitForOtherPromisesToFinish()
|
await waitForOtherPromisesToFinish()
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { ApiError } from '../../../api/common/api-error'
|
import { ApiError } from '../../../api/common/api-error'
|
||||||
import * as getNoteModule from '../../../api/notes'
|
import * as getNoteModule from '../../../api/notes'
|
||||||
import type { Note } from '../../../api/notes/types'
|
|
||||||
import * as LoadingScreenModule from '../../../components/application-loader/loading-screen/loading-screen'
|
import * as LoadingScreenModule from '../../../components/application-loader/loading-screen/loading-screen'
|
||||||
import * as setNoteDataFromServerModule from '../../../redux/note-details/methods'
|
import * as setNoteDataFromServerModule from '../../../redux/note-details/methods'
|
||||||
import { mockI18n } from '../../../test-utils/mock-i18n'
|
import { mockI18n } from '../../../test-utils/mock-i18n'
|
||||||
|
@ -16,6 +15,7 @@ import { NoteLoadingBoundary } from './note-loading-boundary'
|
||||||
import { render, screen } from '@testing-library/react'
|
import { render, screen } from '@testing-library/react'
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
import { Mock } from 'ts-mockery'
|
import { Mock } from 'ts-mockery'
|
||||||
|
import type { NoteDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
jest.mock('../../../hooks/common/use-single-string-url-parameter')
|
jest.mock('../../../hooks/common/use-single-string-url-parameter')
|
||||||
jest.mock('../../../api/notes')
|
jest.mock('../../../api/notes')
|
||||||
|
@ -65,7 +65,7 @@ describe('Note loading boundary', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
const mockGetNoteApiCall = (returnValue: Note) => {
|
const mockGetNoteApiCall = (returnValue: NoteDto) => {
|
||||||
jest.spyOn(getNoteModule, 'getNote').mockImplementation((id) => {
|
jest.spyOn(getNoteModule, 'getNote').mockImplementation((id) => {
|
||||||
expect(id).toBe(mockedNoteId)
|
expect(id).toBe(mockedNoteId)
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
|
@ -83,14 +83,14 @@ describe('Note loading boundary', () => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const mockSetNoteInRedux = (expectedNote: Note): jest.SpyInstance<void, [apiResponse: Note]> => {
|
const mockSetNoteInRedux = (expectedNote: NoteDto): jest.SpyInstance<void, [apiResponse: NoteDto]> => {
|
||||||
return jest.spyOn(setNoteDataFromServerModule, 'setNoteDataFromServer').mockImplementation((givenNote) => {
|
return jest.spyOn(setNoteDataFromServerModule, 'setNoteDataFromServer').mockImplementation((givenNote) => {
|
||||||
expect(givenNote).toBe(expectedNote)
|
expect(givenNote).toBe(expectedNote)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
it('loads a note', async () => {
|
it('loads a note', async () => {
|
||||||
const mockedNote: Note = Mock.of<Note>()
|
const mockedNote: NoteDto = Mock.of<NoteDto>()
|
||||||
mockGetNoteApiCall(mockedNote)
|
mockGetNoteApiCall(mockedNote)
|
||||||
const setNoteInReduxFunctionMock = mockSetNoteInRedux(mockedNote)
|
const setNoteInReduxFunctionMock = mockSetNoteInRedux(mockedNote)
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ describe('Note loading boundary', () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
it('shows an error', async () => {
|
it('shows an error', async () => {
|
||||||
const mockedNote: Note = Mock.of<Note>()
|
const mockedNote: NoteDto = Mock.of<NoteDto>()
|
||||||
mockCrashingNoteApiCall()
|
mockCrashingNoteApiCall()
|
||||||
const setNoteInReduxFunctionMock = mockSetNoteInRedux(mockedNote)
|
const setNoteInReduxFunctionMock = mockSetNoteInRedux(mockedNote)
|
||||||
|
|
||||||
|
|
|
@ -116,7 +116,7 @@ exports[`UserAvatar uses custom photo component if provided 1`] = `
|
||||||
<span
|
<span
|
||||||
class="ms-2 me-1 user-line-name"
|
class="ms-2 me-1 user-line-name"
|
||||||
>
|
>
|
||||||
test
|
No face user
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -133,7 +133,7 @@ exports[`UserAvatar uses custom photo component preferred over photoUrl 1`] = `
|
||||||
<span
|
<span
|
||||||
class="ms-2 me-1 user-line-name"
|
class="ms-2 me-1 user-line-name"
|
||||||
>
|
>
|
||||||
test
|
Boaty McBoatFace
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -155,7 +155,7 @@ exports[`UserAvatar uses identicon when empty photoUrl is given 1`] = `
|
||||||
<span
|
<span
|
||||||
class="ms-2 me-1 user-line-name"
|
class="ms-2 me-1 user-line-name"
|
||||||
>
|
>
|
||||||
test
|
Empty
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -177,7 +177,7 @@ exports[`UserAvatar uses identicon when no photoUrl is given 1`] = `
|
||||||
<span
|
<span
|
||||||
class="ms-2 me-1 user-line-name"
|
class="ms-2 me-1 user-line-name"
|
||||||
>
|
>
|
||||||
test
|
No face user
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,21 +1,29 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import type { UserAvatarProps } from './user-avatar'
|
|
||||||
import { UserAvatar } from './user-avatar'
|
import { UserAvatar } from './user-avatar'
|
||||||
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
|
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
|
||||||
import { Person as IconPerson } from 'react-bootstrap-icons'
|
import { Person as IconPerson } from 'react-bootstrap-icons'
|
||||||
|
import type { CommonUserAvatarProps } from './types'
|
||||||
export type GuestUserAvatarProps = Omit<UserAvatarProps, 'displayName' | 'photoUrl' | 'username'>
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The avatar component for an anonymous user.
|
* The avatar component for an anonymous user.
|
||||||
* @param props The properties of the guest user avatar ({@link UserAvatarProps})
|
* @param props The properties of the guest user avatar ({@link UserAvatarProps})
|
||||||
*/
|
*/
|
||||||
export const GuestUserAvatar: React.FC<GuestUserAvatarProps> = (props) => {
|
export const GuestUserAvatar: React.FC<CommonUserAvatarProps> = (props) => {
|
||||||
const label = useTranslatedText('common.guestUser')
|
const label = useTranslatedText('common.guestUser')
|
||||||
return <UserAvatar displayName={label} photoComponent={<IconPerson />} {...props} />
|
return (
|
||||||
|
<UserAvatar
|
||||||
|
user={{
|
||||||
|
username: '',
|
||||||
|
photoUrl: null,
|
||||||
|
displayName: label
|
||||||
|
}}
|
||||||
|
photoComponent={<IconPerson />}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,28 +1,30 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { useMemo } from 'react'
|
import { useMemo } from 'react'
|
||||||
import { createAvatar } from '@dicebear/core'
|
import { createAvatar } from '@dicebear/core'
|
||||||
import * as identicon from '@dicebear/identicon'
|
import * as identicon from '@dicebear/identicon'
|
||||||
|
import type { UserInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the correct avatar url for a user.
|
* Returns the correct avatar url for a user.
|
||||||
* When an empty or no photoUrl is given, a random avatar is generated from the displayName.
|
* When the user has no photoUrl, a random avatar is generated from the display name.
|
||||||
*
|
*
|
||||||
* @param photoUrl The photo url of the user to use. Maybe empty or not set.
|
* @param user The user for which to get the avatar URL
|
||||||
* @param username The username of the user to use as input to the random avatar.
|
* @return The correct avatar url for the user
|
||||||
* @return The correct avatar url for the user.
|
|
||||||
*/
|
*/
|
||||||
export const useAvatarUrl = (photoUrl: string | undefined, username: string): string => {
|
export const useAvatarUrl = (user: UserInfoDto): string => {
|
||||||
|
const { photoUrl, displayName } = user
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
if (photoUrl && photoUrl.trim() !== '') {
|
if (photoUrl && photoUrl.trim() !== '') {
|
||||||
return photoUrl
|
return photoUrl
|
||||||
}
|
}
|
||||||
const avatar = createAvatar(identicon, {
|
const avatar = createAvatar(identicon, {
|
||||||
seed: username
|
seed: displayName
|
||||||
})
|
})
|
||||||
return avatar.toDataUri()
|
return avatar.toDataUri()
|
||||||
}, [photoUrl, username])
|
}, [photoUrl, displayName])
|
||||||
}
|
}
|
||||||
|
|
14
frontend/src/components/common/user-avatar/types.ts
Normal file
14
frontend/src/components/common/user-avatar/types.ts
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
import type React from 'react'
|
||||||
|
|
||||||
|
export interface CommonUserAvatarProps {
|
||||||
|
size?: 'sm' | 'lg'
|
||||||
|
additionalClasses?: string
|
||||||
|
showName?: boolean
|
||||||
|
photoComponent?: React.ReactNode
|
||||||
|
overrideDisplayName?: string
|
||||||
|
}
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
*/
|
|
||||||
import type { UserInfo } from '../../../api/users/types'
|
|
||||||
import type { UserAvatarProps } from './user-avatar'
|
|
||||||
import { UserAvatar } from './user-avatar'
|
|
||||||
import React from 'react'
|
|
||||||
|
|
||||||
export interface UserAvatarForUserProps extends Omit<UserAvatarProps, 'photoUrl' | 'displayName'> {
|
|
||||||
user: UserInfo
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Renders the avatar image of a user, optionally altogether with their name.
|
|
||||||
*
|
|
||||||
* @param user The user object with the display name and photo.
|
|
||||||
* @param props remaining avatar props
|
|
||||||
*/
|
|
||||||
export const UserAvatarForUser: React.FC<UserAvatarForUserProps> = ({ user, ...props }) => {
|
|
||||||
return <UserAvatar displayName={user.displayName} photoUrl={user.photoUrl} username={user.username} {...props} />
|
|
||||||
}
|
|
|
@ -1,18 +1,18 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { getUserInfo } from '../../../api/users'
|
import { getUserInfo } from '../../../api/users'
|
||||||
import { AsyncLoadingBoundary } from '../async-loading-boundary/async-loading-boundary'
|
import { AsyncLoadingBoundary } from '../async-loading-boundary/async-loading-boundary'
|
||||||
import type { UserAvatarProps } from './user-avatar'
|
|
||||||
import { UserAvatar } from './user-avatar'
|
import { UserAvatar } from './user-avatar'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { useAsync } from 'react-use'
|
import { useAsync } from 'react-use'
|
||||||
import type { UserInfo } from '../../../api/users/types'
|
import type { CommonUserAvatarProps } from './types'
|
||||||
|
import type { UserInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export interface UserAvatarForUsernameProps extends Omit<UserAvatarProps, 'photoUrl' | 'displayName'> {
|
export interface UserAvatarForUsernameProps extends CommonUserAvatarProps {
|
||||||
username: string | null
|
username: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,12 +27,13 @@ export interface UserAvatarForUsernameProps extends Omit<UserAvatarProps, 'photo
|
||||||
*/
|
*/
|
||||||
export const UserAvatarForUsername: React.FC<UserAvatarForUsernameProps> = ({ username, ...props }) => {
|
export const UserAvatarForUsername: React.FC<UserAvatarForUsernameProps> = ({ username, ...props }) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { error, value, loading } = useAsync(async (): Promise<UserInfo> => {
|
const { error, value, loading } = useAsync(async (): Promise<UserInfoDto> => {
|
||||||
return username
|
return username
|
||||||
? await getUserInfo(username)
|
? await getUserInfo(username)
|
||||||
: {
|
: {
|
||||||
displayName: t('common.guestUser'),
|
displayName: t('common.guestUser'),
|
||||||
username: ''
|
username: '',
|
||||||
|
photoUrl: null
|
||||||
}
|
}
|
||||||
}, [username, t])
|
}, [username, t])
|
||||||
|
|
||||||
|
@ -40,8 +41,8 @@ export const UserAvatarForUsername: React.FC<UserAvatarForUsernameProps> = ({ us
|
||||||
if (!value) {
|
if (!value) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
return <UserAvatar displayName={value.displayName} photoUrl={value.photoUrl} username={username} {...props} />
|
return <UserAvatar user={value} {...props} />
|
||||||
}, [props, value, username])
|
}, [props, value])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncLoadingBoundary loading={loading || !value} error={error} componentName={'UserAvatarForUsername'}>
|
<AsyncLoadingBoundary loading={loading || !value} error={error} componentName={'UserAvatarForUsername'}>
|
||||||
|
|
|
@ -1,13 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { UserInfo } from '../../../api/users/types'
|
|
||||||
import { mockI18n } from '../../../test-utils/mock-i18n'
|
import { mockI18n } from '../../../test-utils/mock-i18n'
|
||||||
import { UserAvatarForUser } from './user-avatar-for-user'
|
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import { UserAvatar } from './user-avatar'
|
import { UserAvatar } from './user-avatar'
|
||||||
|
import type { UserInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
jest.mock('@dicebear/identicon', () => null)
|
jest.mock('@dicebear/identicon', () => null)
|
||||||
jest.mock('@dicebear/core', () => ({
|
jest.mock('@dicebear/core', () => ({
|
||||||
|
@ -17,58 +16,68 @@ jest.mock('@dicebear/core', () => ({
|
||||||
}))
|
}))
|
||||||
|
|
||||||
describe('UserAvatar', () => {
|
describe('UserAvatar', () => {
|
||||||
const user: UserInfo = {
|
const user: UserInfoDto = {
|
||||||
username: 'boatface',
|
username: 'boatface',
|
||||||
displayName: 'Boaty McBoatFace',
|
displayName: 'Boaty McBoatFace',
|
||||||
photoUrl: 'https://example.com/test.png'
|
photoUrl: 'https://example.com/test.png'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userWithoutPhoto: UserInfoDto = {
|
||||||
|
username: 'pictureless',
|
||||||
|
displayName: 'No face user',
|
||||||
|
photoUrl: null
|
||||||
|
}
|
||||||
|
|
||||||
|
const userWithEmptyPhoto: UserInfoDto = {
|
||||||
|
username: 'void',
|
||||||
|
displayName: 'Empty',
|
||||||
|
photoUrl: ''
|
||||||
|
}
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
await mockI18n()
|
await mockI18n()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('renders the user avatar correctly', () => {
|
it('renders the user avatar correctly', () => {
|
||||||
const view = render(<UserAvatarForUser user={user} />)
|
const view = render(<UserAvatar user={user} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
describe('renders the user avatar in size', () => {
|
describe('renders the user avatar in size', () => {
|
||||||
it('sm', () => {
|
it('sm', () => {
|
||||||
const view = render(<UserAvatarForUser user={user} size={'sm'} />)
|
const view = render(<UserAvatar user={user} size={'sm'} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
it('lg', () => {
|
it('lg', () => {
|
||||||
const view = render(<UserAvatarForUser user={user} size={'lg'} />)
|
const view = render(<UserAvatar user={user} size={'lg'} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
it('adds additionalClasses props to wrapping span', () => {
|
it('adds additionalClasses props to wrapping span', () => {
|
||||||
const view = render(<UserAvatarForUser user={user} additionalClasses={'testClass'} />)
|
const view = render(<UserAvatar user={user} additionalClasses={'testClass'} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
it('does not show names if showName prop is false', () => {
|
it('does not show names if showName prop is false', () => {
|
||||||
const view = render(<UserAvatarForUser user={user} showName={false} />)
|
const view = render(<UserAvatar user={user} showName={false} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses identicon when no photoUrl is given', () => {
|
it('uses identicon when no photoUrl is given', () => {
|
||||||
const view = render(<UserAvatar displayName={'test'} />)
|
const view = render(<UserAvatar user={userWithoutPhoto} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses identicon when empty photoUrl is given', () => {
|
it('uses identicon when empty photoUrl is given', () => {
|
||||||
const view = render(<UserAvatar displayName={'test'} photoUrl={''} />)
|
const view = render(<UserAvatar user={userWithEmptyPhoto} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses custom photo component if provided', () => {
|
it('uses custom photo component if provided', () => {
|
||||||
const view = render(<UserAvatar displayName={'test'} photoComponent={<div>Custom Photo</div>} />)
|
const view = render(<UserAvatar user={userWithoutPhoto} photoComponent={<div>Custom Photo</div>} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses custom photo component preferred over photoUrl', () => {
|
it('uses custom photo component preferred over photoUrl', () => {
|
||||||
const view = render(
|
const view = render(<UserAvatar user={user} photoComponent={<div>Custom Photo</div>} />)
|
||||||
<UserAvatar displayName={'test'} photoComponent={<div>Custom Photo</div>} photoUrl={user.photoUrl} />
|
|
||||||
)
|
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -7,15 +7,11 @@ import { useTranslatedText } from '../../../hooks/common/use-translated-text'
|
||||||
import styles from './user-avatar.module.scss'
|
import styles from './user-avatar.module.scss'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { useAvatarUrl } from './hooks/use-avatar-url'
|
import { useAvatarUrl } from './hooks/use-avatar-url'
|
||||||
|
import type { UserInfoDto } from '@hedgedoc/commons'
|
||||||
|
import type { CommonUserAvatarProps } from './types'
|
||||||
|
|
||||||
export interface UserAvatarProps {
|
interface UserAvatarProps extends CommonUserAvatarProps {
|
||||||
size?: 'sm' | 'lg'
|
user: UserInfoDto
|
||||||
additionalClasses?: string
|
|
||||||
showName?: boolean
|
|
||||||
photoUrl?: string
|
|
||||||
displayName: string
|
|
||||||
username?: string | null
|
|
||||||
photoComponent?: React.ReactNode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -25,17 +21,16 @@ export interface UserAvatarProps {
|
||||||
* @param size The size in which the user image should be shown.
|
* @param size The size in which the user image should be shown.
|
||||||
* @param additionalClasses Additional CSS classes that will be added to the container.
|
* @param additionalClasses Additional CSS classes that will be added to the container.
|
||||||
* @param showName true when the name should be displayed alongside the image, false otherwise. Defaults to true.
|
* @param showName true when the name should be displayed alongside the image, false otherwise. Defaults to true.
|
||||||
* @param username The username to use for generating the fallback avatar image.
|
|
||||||
* @param photoComponent A custom component to use as the user's photo.
|
* @param photoComponent A custom component to use as the user's photo.
|
||||||
|
* @param overrideDisplayName Used to override the used display name, for example for setting random guest names
|
||||||
*/
|
*/
|
||||||
export const UserAvatar: React.FC<UserAvatarProps> = ({
|
export const UserAvatar: React.FC<UserAvatarProps> = ({
|
||||||
photoUrl,
|
|
||||||
displayName,
|
|
||||||
size,
|
size,
|
||||||
additionalClasses = '',
|
additionalClasses = '',
|
||||||
showName = true,
|
showName = true,
|
||||||
username,
|
photoComponent,
|
||||||
photoComponent
|
user,
|
||||||
|
overrideDisplayName
|
||||||
}) => {
|
}) => {
|
||||||
const imageSize = useMemo(() => {
|
const imageSize = useMemo(() => {
|
||||||
switch (size) {
|
switch (size) {
|
||||||
|
@ -48,13 +43,21 @@ export const UserAvatar: React.FC<UserAvatarProps> = ({
|
||||||
}
|
}
|
||||||
}, [size])
|
}, [size])
|
||||||
|
|
||||||
const avatarUrl = useAvatarUrl(photoUrl, username ?? displayName)
|
const modifiedUser: UserInfoDto = useMemo(
|
||||||
|
() => ({
|
||||||
|
...user,
|
||||||
|
displayName: overrideDisplayName ?? user.displayName
|
||||||
|
}),
|
||||||
|
[user, overrideDisplayName]
|
||||||
|
)
|
||||||
|
|
||||||
|
const avatarUrl = useAvatarUrl(modifiedUser)
|
||||||
|
|
||||||
const imageTranslateOptions = useMemo(
|
const imageTranslateOptions = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
name: displayName
|
name: modifiedUser.displayName
|
||||||
}),
|
}),
|
||||||
[displayName]
|
[modifiedUser.displayName]
|
||||||
)
|
)
|
||||||
const imgDescription = useTranslatedText('common.avatarOf', imageTranslateOptions)
|
const imgDescription = useTranslatedText('common.avatarOf', imageTranslateOptions)
|
||||||
|
|
||||||
|
@ -71,7 +74,7 @@ export const UserAvatar: React.FC<UserAvatarProps> = ({
|
||||||
width={imageSize}
|
width={imageSize}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{showName && <span className={`ms-2 me-1 ${styles['user-line-name']}`}>{displayName}</span>}
|
{showName && <span className={`ms-2 me-1 ${styles['user-line-name']}`}>{modifiedUser.displayName}</span>}
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { LoginUserInfo } from '../../../../api/me/types'
|
|
||||||
import { useDisconnectOnUserLoginStatusChange } from './use-disconnect-on-user-login-status-change'
|
import { useDisconnectOnUserLoginStatusChange } from './use-disconnect-on-user-login-status-change'
|
||||||
import type { MessageTransporter } from '@hedgedoc/commons'
|
import type { LoginUserInfoDto, MessageTransporter } from '@hedgedoc/commons'
|
||||||
import { render } from '@testing-library/react'
|
import { render } from '@testing-library/react'
|
||||||
import React, { Fragment } from 'react'
|
import React, { Fragment } from 'react'
|
||||||
import { Mock } from 'ts-mockery'
|
import { Mock } from 'ts-mockery'
|
||||||
|
@ -21,7 +20,7 @@ describe('use logout on user change', () => {
|
||||||
|
|
||||||
const mockUseApplicationState = (userLoggedIn: boolean) => {
|
const mockUseApplicationState = (userLoggedIn: boolean) => {
|
||||||
mockAppState({
|
mockAppState({
|
||||||
user: userLoggedIn ? Mock.of<LoginUserInfo>({}) : null
|
user: userLoggedIn ? Mock.of<LoginUserInfoDto>({}) : null
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -36,7 +36,8 @@ describe('frontend websocket', () => {
|
||||||
mockSocket()
|
mockSocket()
|
||||||
const handler = jest.fn((reason?: DisconnectReason) => console.log(reason))
|
const handler = jest.fn((reason?: DisconnectReason) => console.log(reason))
|
||||||
|
|
||||||
let modifiedHandler: (event: CloseEvent) => void = jest.fn()
|
let modifiedHandler: EventListenerOrEventListenerObject = jest.fn()
|
||||||
|
|
||||||
jest.spyOn(mockedSocket, 'addEventListener').mockImplementation((event, handler_) => {
|
jest.spyOn(mockedSocket, 'addEventListener').mockImplementation((event, handler_) => {
|
||||||
modifiedHandler = handler_
|
modifiedHandler = handler_
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { addLink } from './add-link'
|
import { addLink } from './add-link'
|
||||||
import type { ContentEdits } from './changes'
|
import type { ContentEdits } from './types/changes'
|
||||||
|
|
||||||
describe('add link', () => {
|
describe('add link', () => {
|
||||||
describe('without to-cursor', () => {
|
describe('without to-cursor', () => {
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import * as AliasModule from '../../../../../../api/alias'
|
import * as AliasModule from '../../../../../../api/alias'
|
||||||
import type { Alias } from '../../../../../../api/alias/types'
|
|
||||||
import * as NoteDetailsReduxModule from '../../../../../../redux/note-details/methods'
|
import * as NoteDetailsReduxModule from '../../../../../../redux/note-details/methods'
|
||||||
import { mockI18n } from '../../../../../../test-utils/mock-i18n'
|
import { mockI18n } from '../../../../../../test-utils/mock-i18n'
|
||||||
import { mockNotePermissions } from '../../../../../../test-utils/mock-note-permissions'
|
import { mockNotePermissions } from '../../../../../../test-utils/mock-note-permissions'
|
||||||
|
@ -12,6 +11,7 @@ import { AliasesListEntry } from './aliases-list-entry'
|
||||||
import { act, render, screen } from '@testing-library/react'
|
import { act, render, screen } from '@testing-library/react'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { mockUiNotifications } from '../../../../../../test-utils/mock-ui-notifications'
|
import { mockUiNotifications } from '../../../../../../test-utils/mock-ui-notifications'
|
||||||
|
import type { AliasDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
jest.mock('../../../../../../api/alias')
|
jest.mock('../../../../../../api/alias')
|
||||||
jest.mock('../../../../../../redux/note-details/methods')
|
jest.mock('../../../../../../redux/note-details/methods')
|
||||||
|
@ -37,7 +37,7 @@ describe('AliasesListEntry', () => {
|
||||||
|
|
||||||
it('renders an AliasesListEntry that is primary', async () => {
|
it('renders an AliasesListEntry that is primary', async () => {
|
||||||
mockNotePermissions('test', 'test')
|
mockNotePermissions('test', 'test')
|
||||||
const testAlias: Alias = {
|
const testAlias: AliasDto = {
|
||||||
name: 'test-primary',
|
name: 'test-primary',
|
||||||
primaryAlias: true,
|
primaryAlias: true,
|
||||||
noteId: 'test-note-id'
|
noteId: 'test-note-id'
|
||||||
|
@ -55,7 +55,7 @@ describe('AliasesListEntry', () => {
|
||||||
|
|
||||||
it("adds aliasPrimaryBadge & removes aliasButtonMakePrimary in AliasesListEntry if it's primary", () => {
|
it("adds aliasPrimaryBadge & removes aliasButtonMakePrimary in AliasesListEntry if it's primary", () => {
|
||||||
mockNotePermissions('test2', 'test')
|
mockNotePermissions('test2', 'test')
|
||||||
const testAlias: Alias = {
|
const testAlias: AliasDto = {
|
||||||
name: 'test-primary',
|
name: 'test-primary',
|
||||||
primaryAlias: true,
|
primaryAlias: true,
|
||||||
noteId: 'test-note-id'
|
noteId: 'test-note-id'
|
||||||
|
@ -66,7 +66,7 @@ describe('AliasesListEntry', () => {
|
||||||
|
|
||||||
it('renders an AliasesListEntry that is not primary', async () => {
|
it('renders an AliasesListEntry that is not primary', async () => {
|
||||||
mockNotePermissions('test', 'test')
|
mockNotePermissions('test', 'test')
|
||||||
const testAlias: Alias = {
|
const testAlias: AliasDto = {
|
||||||
name: 'test-non-primary',
|
name: 'test-non-primary',
|
||||||
primaryAlias: false,
|
primaryAlias: false,
|
||||||
noteId: 'test-note-id'
|
noteId: 'test-note-id'
|
||||||
|
@ -91,7 +91,7 @@ describe('AliasesListEntry', () => {
|
||||||
|
|
||||||
it("removes aliasPrimaryBadge & adds aliasButtonMakePrimary in AliasesListEntry if it's not primary", () => {
|
it("removes aliasPrimaryBadge & adds aliasButtonMakePrimary in AliasesListEntry if it's not primary", () => {
|
||||||
mockNotePermissions('test2', 'test')
|
mockNotePermissions('test2', 'test')
|
||||||
const testAlias: Alias = {
|
const testAlias: AliasDto = {
|
||||||
name: 'test-primary',
|
name: 'test-primary',
|
||||||
primaryAlias: false,
|
primaryAlias: false,
|
||||||
noteId: 'test-note-id'
|
noteId: 'test-note-id'
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { deleteAlias, markAliasAsPrimary } from '../../../../../../api/alias'
|
import { deleteAlias, markAliasAsPrimary } from '../../../../../../api/alias'
|
||||||
import type { Alias } from '../../../../../../api/alias/types'
|
|
||||||
import { useIsOwner } from '../../../../../../hooks/common/use-is-owner'
|
import { useIsOwner } from '../../../../../../hooks/common/use-is-owner'
|
||||||
import { useTranslatedText } from '../../../../../../hooks/common/use-translated-text'
|
import { useTranslatedText } from '../../../../../../hooks/common/use-translated-text'
|
||||||
import { updateMetadata } from '../../../../../../redux/note-details/methods'
|
import { updateMetadata } from '../../../../../../redux/note-details/methods'
|
||||||
|
@ -16,9 +15,10 @@ import { Badge } from 'react-bootstrap'
|
||||||
import { Button } from 'react-bootstrap'
|
import { Button } from 'react-bootstrap'
|
||||||
import { Star as IconStar, X as IconX } from 'react-bootstrap-icons'
|
import { Star as IconStar, X as IconX } from 'react-bootstrap-icons'
|
||||||
import { useTranslation, Trans } from 'react-i18next'
|
import { useTranslation, Trans } from 'react-i18next'
|
||||||
|
import type { AliasDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export interface AliasesListEntryProps {
|
export interface AliasesListEntryProps {
|
||||||
alias: Alias
|
alias: AliasDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
|
||||||
import type { Alias } from '../../../../../../api/alias/types'
|
|
||||||
import type { ApplicationState } from '../../../../../../redux'
|
import type { ApplicationState } from '../../../../../../redux'
|
||||||
import { AliasesListEntry } from './aliases-list-entry'
|
import { AliasesListEntry } from './aliases-list-entry'
|
||||||
import React, { Fragment, useMemo } from 'react'
|
import React, { Fragment, useMemo } from 'react'
|
||||||
|
import type { AliasDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the list of aliases.
|
* Renders the list of aliases.
|
||||||
|
@ -18,8 +18,8 @@ export const AliasesList: React.FC = () => {
|
||||||
return aliases === undefined
|
return aliases === undefined
|
||||||
? null
|
? null
|
||||||
: Object.assign([], aliases)
|
: Object.assign([], aliases)
|
||||||
.sort((a: Alias, b: Alias) => a.name.localeCompare(b.name))
|
.sort((a: AliasDto, b: AliasDto) => a.name.localeCompare(b.name))
|
||||||
.map((alias: Alias) => <AliasesListEntry alias={alias} key={alias.name} />)
|
.map((alias: AliasDto) => <AliasesListEntry alias={alias} key={alias.name} />)
|
||||||
}, [aliases])
|
}, [aliases])
|
||||||
|
|
||||||
return <Fragment>{aliasesDom}</Fragment>
|
return <Fragment>{aliasesDom}</Fragment>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -15,9 +15,9 @@ import { useApplicationState } from '../../../../../hooks/common/use-application
|
||||||
import { getMediaForNote } from '../../../../../api/notes'
|
import { getMediaForNote } from '../../../../../api/notes'
|
||||||
import { AsyncLoadingBoundary } from '../../../../common/async-loading-boundary/async-loading-boundary'
|
import { AsyncLoadingBoundary } from '../../../../common/async-loading-boundary/async-loading-boundary'
|
||||||
import { MediaEntry } from './media-entry'
|
import { MediaEntry } from './media-entry'
|
||||||
import type { MediaUpload } from '../../../../../api/media/types'
|
|
||||||
import { MediaEntryDeletionModal } from './media-entry-deletion-modal'
|
import { MediaEntryDeletionModal } from './media-entry-deletion-modal'
|
||||||
import { MediaBrowserEmpty } from './media-browser-empty'
|
import { MediaBrowserEmpty } from './media-browser-empty'
|
||||||
|
import type { MediaUploadDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the media browser "menu" for the sidebar.
|
* Renders the media browser "menu" for the sidebar.
|
||||||
|
@ -35,7 +35,7 @@ export const MediaBrowserSidebarMenu: React.FC<SpecificSidebarMenuProps> = ({
|
||||||
}) => {
|
}) => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const noteId = useApplicationState((state) => state.noteDetails?.id ?? '')
|
const noteId = useApplicationState((state) => state.noteDetails?.id ?? '')
|
||||||
const [mediaEntryForDeletion, setMediaEntryForDeletion] = useState<MediaUpload | null>(null)
|
const [mediaEntryForDeletion, setMediaEntryForDeletion] = useState<MediaUploadDto | null>(null)
|
||||||
|
|
||||||
const hide = selectedMenuId !== DocumentSidebarMenuSelection.NONE && selectedMenuId !== menuId
|
const hide = selectedMenuId !== DocumentSidebarMenuSelection.NONE && selectedMenuId !== menuId
|
||||||
const expand = selectedMenuId === menuId
|
const expand = selectedMenuId === menuId
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import type { MediaUpload } from '../../../../../api/media/types'
|
|
||||||
import { useBaseUrl } from '../../../../../hooks/common/use-base-url'
|
import { useBaseUrl } from '../../../../../hooks/common/use-base-url'
|
||||||
import { Button, ButtonGroup } from 'react-bootstrap'
|
import { Button, ButtonGroup } from 'react-bootstrap'
|
||||||
import {
|
import {
|
||||||
|
@ -20,10 +19,11 @@ import { UserAvatarForUsername } from '../../../../common/user-avatar/user-avata
|
||||||
import { useChangeEditorContentCallback } from '../../../change-content-context/use-change-editor-content-callback'
|
import { useChangeEditorContentCallback } from '../../../change-content-context/use-change-editor-content-callback'
|
||||||
import { replaceSelection } from '../../../editor-pane/tool-bar/formatters/replace-selection'
|
import { replaceSelection } from '../../../editor-pane/tool-bar/formatters/replace-selection'
|
||||||
import styles from './media-entry.module.css'
|
import styles from './media-entry.module.css'
|
||||||
|
import type { MediaUploadDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export interface MediaEntryProps {
|
export interface MediaEntryProps {
|
||||||
entry: MediaUpload
|
entry: MediaUploadDto
|
||||||
onDelete: (entry: MediaUpload) => void
|
onDelete: (entry: MediaUploadDto) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { useTranslatedText } from '../../../../../../hooks/common/use-translated-text'
|
import { useTranslatedText } from '../../../../../../hooks/common/use-translated-text'
|
||||||
import { UiIcon } from '../../../../../common/icons/ui-icon'
|
import { UiIcon } from '../../../../../common/icons/ui-icon'
|
||||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||||
import { AccessLevel } from '@hedgedoc/commons'
|
import { GuestAccess } from '@hedgedoc/commons'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { Button, ToggleButtonGroup } from 'react-bootstrap'
|
import { Button, ToggleButtonGroup } from 'react-bootstrap'
|
||||||
import { Eye as IconEye, Pencil as IconPencil, X as IconX } from 'react-bootstrap-icons'
|
import { Eye as IconEye, Pencil as IconPencil, X as IconX } from 'react-bootstrap-icons'
|
||||||
|
@ -24,7 +24,7 @@ export enum PermissionType {
|
||||||
|
|
||||||
export interface PermissionEntryButtonsProps {
|
export interface PermissionEntryButtonsProps {
|
||||||
type: PermissionType
|
type: PermissionType
|
||||||
currentSetting: AccessLevel
|
currentSetting: GuestAccess
|
||||||
name: string
|
name: string
|
||||||
onSetReadOnly: () => void
|
onSetReadOnly: () => void
|
||||||
onSetWriteable: () => void
|
onSetWriteable: () => void
|
||||||
|
@ -79,14 +79,14 @@ export const PermissionEntryButtons: React.FC<PermissionEntryButtonsProps & Perm
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
title={setReadOnlyTitle}
|
title={setReadOnlyTitle}
|
||||||
variant={currentSetting === AccessLevel.READ_ONLY ? 'secondary' : 'outline-secondary'}
|
variant={currentSetting === GuestAccess.READ ? 'secondary' : 'outline-secondary'}
|
||||||
onClick={onSetReadOnly}>
|
onClick={onSetReadOnly}>
|
||||||
<UiIcon icon={IconEye} />
|
<UiIcon icon={IconEye} />
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
title={setWritableTitle}
|
title={setWritableTitle}
|
||||||
variant={currentSetting === AccessLevel.WRITEABLE ? 'secondary' : 'outline-secondary'}
|
variant={currentSetting === GuestAccess.WRITE ? 'secondary' : 'outline-secondary'}
|
||||||
onClick={onSetWriteable}>
|
onClick={onSetWriteable}>
|
||||||
<UiIcon icon={IconPencil} />
|
<UiIcon icon={IconPencil} />
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -10,7 +10,7 @@ import { setNotePermissionsFromServer } from '../../../../../../redux/note-detai
|
||||||
import { IconButton } from '../../../../../common/icon-button/icon-button'
|
import { IconButton } from '../../../../../common/icon-button/icon-button'
|
||||||
import { useUiNotifications } from '../../../../../notifications/ui-notification-boundary'
|
import { useUiNotifications } from '../../../../../notifications/ui-notification-boundary'
|
||||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||||
import { AccessLevel, SpecialGroup } from '@hedgedoc/commons'
|
import { GuestAccess, SpecialGroup } from '@hedgedoc/commons'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { ToggleButtonGroup } from 'react-bootstrap'
|
import { ToggleButtonGroup } from 'react-bootstrap'
|
||||||
import { Eye as IconEye, Pencil as IconPencil, SlashCircle as IconSlashCircle } from 'react-bootstrap-icons'
|
import { Eye as IconEye, Pencil as IconPencil, SlashCircle as IconSlashCircle } from 'react-bootstrap-icons'
|
||||||
|
@ -19,7 +19,7 @@ import { PermissionInconsistentAlert } from './permission-inconsistent-alert'
|
||||||
import { cypressId } from '../../../../../../utils/cypress-attribute'
|
import { cypressId } from '../../../../../../utils/cypress-attribute'
|
||||||
|
|
||||||
export interface PermissionEntrySpecialGroupProps {
|
export interface PermissionEntrySpecialGroupProps {
|
||||||
level: AccessLevel
|
level: GuestAccess
|
||||||
type: SpecialGroup
|
type: SpecialGroup
|
||||||
inconsistent?: boolean
|
inconsistent?: boolean
|
||||||
}
|
}
|
||||||
|
@ -98,7 +98,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={IconSlashCircle}
|
icon={IconSlashCircle}
|
||||||
title={denyGroupText}
|
title={denyGroupText}
|
||||||
variant={level === AccessLevel.NONE ? 'secondary' : 'outline-secondary'}
|
variant={level === GuestAccess.DENY ? 'secondary' : 'outline-secondary'}
|
||||||
onClick={onSetEntryDenied}
|
onClick={onSetEntryDenied}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={'p-1'}
|
className={'p-1'}
|
||||||
|
@ -107,7 +107,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={IconEye}
|
icon={IconEye}
|
||||||
title={viewOnlyGroupText}
|
title={viewOnlyGroupText}
|
||||||
variant={level === AccessLevel.READ_ONLY ? 'secondary' : 'outline-secondary'}
|
variant={level === GuestAccess.READ ? 'secondary' : 'outline-secondary'}
|
||||||
onClick={onSetEntryReadOnly}
|
onClick={onSetEntryReadOnly}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={'p-1'}
|
className={'p-1'}
|
||||||
|
@ -116,7 +116,7 @@ export const PermissionEntrySpecialGroup: React.FC<PermissionEntrySpecialGroupPr
|
||||||
<IconButton
|
<IconButton
|
||||||
icon={IconPencil}
|
icon={IconPencil}
|
||||||
title={editGroupText}
|
title={editGroupText}
|
||||||
variant={level === AccessLevel.WRITEABLE ? 'secondary' : 'outline-secondary'}
|
variant={level === GuestAccess.WRITE ? 'secondary' : 'outline-secondary'}
|
||||||
onClick={onSetEntryWriteable}
|
onClick={onSetEntryWriteable}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
className={'p-1'}
|
className={'p-1'}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -7,20 +7,20 @@ import { removeUserPermission, setUserPermission } from '../../../../../../api/p
|
||||||
import { getUserInfo } from '../../../../../../api/users'
|
import { getUserInfo } from '../../../../../../api/users'
|
||||||
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../../../../hooks/common/use-application-state'
|
||||||
import { setNotePermissionsFromServer } from '../../../../../../redux/note-details/methods'
|
import { setNotePermissionsFromServer } from '../../../../../../redux/note-details/methods'
|
||||||
import { UserAvatarForUser } from '../../../../../common/user-avatar/user-avatar-for-user'
|
|
||||||
import { useUiNotifications } from '../../../../../notifications/ui-notification-boundary'
|
import { useUiNotifications } from '../../../../../notifications/ui-notification-boundary'
|
||||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||||
import { PermissionEntryButtons, PermissionType } from './permission-entry-buttons'
|
import { PermissionEntryButtons, PermissionType } from './permission-entry-buttons'
|
||||||
import type { NoteUserPermissionEntry } from '@hedgedoc/commons'
|
import type { NoteUserPermissionEntryDto } from '@hedgedoc/commons'
|
||||||
import { AccessLevel, SpecialGroup } from '@hedgedoc/commons'
|
import { GuestAccess, SpecialGroup } from '@hedgedoc/commons'
|
||||||
import React, { useCallback, useMemo } from 'react'
|
import React, { useCallback, useMemo } from 'react'
|
||||||
import { useAsync } from 'react-use'
|
import { useAsync } from 'react-use'
|
||||||
import { PermissionInconsistentAlert } from './permission-inconsistent-alert'
|
import { PermissionInconsistentAlert } from './permission-inconsistent-alert'
|
||||||
import { useGetSpecialPermissions } from './hooks/use-get-special-permissions'
|
import { useGetSpecialPermissions } from './hooks/use-get-special-permissions'
|
||||||
import { AsyncLoadingBoundary } from '../../../../../common/async-loading-boundary/async-loading-boundary'
|
import { AsyncLoadingBoundary } from '../../../../../common/async-loading-boundary/async-loading-boundary'
|
||||||
|
import { UserAvatar } from '../../../../../common/user-avatar/user-avatar'
|
||||||
|
|
||||||
export interface PermissionEntryUserProps {
|
export interface PermissionEntryUserProps {
|
||||||
entry: NoteUserPermissionEntry
|
entry: NoteUserPermissionEntryDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -89,12 +89,12 @@ export const PermissionEntryUser: React.FC<PermissionEntryUserProps & Permission
|
||||||
return (
|
return (
|
||||||
<AsyncLoadingBoundary loading={loading} error={error} componentName={'PermissionEntryUser'}>
|
<AsyncLoadingBoundary loading={loading} error={error} componentName={'PermissionEntryUser'}>
|
||||||
<li className={'list-group-item d-flex flex-row justify-content-between align-items-center'}>
|
<li className={'list-group-item d-flex flex-row justify-content-between align-items-center'}>
|
||||||
<UserAvatarForUser user={value} />
|
<UserAvatar user={value} />
|
||||||
<div className={'d-flex flex-row align-items-center'}>
|
<div className={'d-flex flex-row align-items-center'}>
|
||||||
<PermissionInconsistentAlert show={permissionInconsistent ?? false} />
|
<PermissionInconsistentAlert show={permissionInconsistent ?? false} />
|
||||||
<PermissionEntryButtons
|
<PermissionEntryButtons
|
||||||
type={PermissionType.USER}
|
type={PermissionType.USER}
|
||||||
currentSetting={entry.canEdit ? AccessLevel.WRITEABLE : AccessLevel.READ_ONLY}
|
currentSetting={entry.canEdit ? GuestAccess.WRITE : GuestAccess.READ}
|
||||||
name={value.displayName}
|
name={value.displayName}
|
||||||
onSetReadOnly={onSetEntryReadOnly}
|
onSetReadOnly={onSetEntryReadOnly}
|
||||||
onSetWriteable={onSetEntryWriteable}
|
onSetWriteable={onSetEntryWriteable}
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { useIsOwner } from '../../../../../../hooks/common/use-is-owner'
|
import { useIsOwner } from '../../../../../../hooks/common/use-is-owner'
|
||||||
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
import type { PermissionDisabledProps } from './permission-disabled.prop'
|
||||||
import { PermissionEntrySpecialGroup } from './permission-entry-special-group'
|
import { PermissionEntrySpecialGroup } from './permission-entry-special-group'
|
||||||
import { AccessLevel, SpecialGroup } from '@hedgedoc/commons'
|
import { GuestAccess, SpecialGroup } from '@hedgedoc/commons'
|
||||||
import React, { Fragment, useMemo } from 'react'
|
import React, { Fragment, useMemo } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useGetSpecialPermissions } from './hooks/use-get-special-permissions'
|
import { useGetSpecialPermissions } from './hooks/use-get-special-permissions'
|
||||||
|
@ -23,16 +23,8 @@ export const PermissionSectionSpecialGroups: React.FC<PermissionDisabledProps> =
|
||||||
|
|
||||||
const specialGroupEntries = useMemo(() => {
|
const specialGroupEntries = useMemo(() => {
|
||||||
return {
|
return {
|
||||||
everyoneLevel: groupEveryone
|
everyoneLevel: groupEveryone ? (groupEveryone.canEdit ? GuestAccess.WRITE : GuestAccess.READ) : GuestAccess.DENY,
|
||||||
? groupEveryone.canEdit
|
loggedInLevel: groupLoggedIn ? (groupLoggedIn.canEdit ? GuestAccess.WRITE : GuestAccess.READ) : GuestAccess.DENY,
|
||||||
? AccessLevel.WRITEABLE
|
|
||||||
: AccessLevel.READ_ONLY
|
|
||||||
: AccessLevel.NONE,
|
|
||||||
loggedInLevel: groupLoggedIn
|
|
||||||
? groupLoggedIn.canEdit
|
|
||||||
? AccessLevel.WRITEABLE
|
|
||||||
: AccessLevel.READ_ONLY
|
|
||||||
: AccessLevel.NONE,
|
|
||||||
loggedInInconsistentAlert: groupEveryone && (!groupLoggedIn || (groupEveryone.canEdit && !groupLoggedIn.canEdit))
|
loggedInInconsistentAlert: groupEveryone && (!groupLoggedIn || (groupEveryone.canEdit && !groupLoggedIn.canEdit))
|
||||||
}
|
}
|
||||||
}, [groupEveryone, groupLoggedIn])
|
}, [groupEveryone, groupLoggedIn])
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { RevisionMetadata } from '../../../../../../api/revisions/types'
|
import type { RevisionMetadataDto } from '@hedgedoc/commons'
|
||||||
import { UiIcon } from '../../../../../common/icons/ui-icon'
|
import { UiIcon } from '../../../../../common/icons/ui-icon'
|
||||||
import { UserAvatarForUser } from '../../../../../common/user-avatar/user-avatar-for-user'
|
|
||||||
import { WaitSpinner } from '../../../../../common/wait-spinner/wait-spinner'
|
import { WaitSpinner } from '../../../../../common/wait-spinner/wait-spinner'
|
||||||
import { useUiNotifications } from '../../../../../notifications/ui-notification-boundary'
|
import { useUiNotifications } from '../../../../../notifications/ui-notification-boundary'
|
||||||
import styles from './revision-list-entry.module.scss'
|
import styles from './revision-list-entry.module.scss'
|
||||||
|
@ -21,11 +20,12 @@ import {
|
||||||
} from 'react-bootstrap-icons'
|
} from 'react-bootstrap-icons'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useAsync } from 'react-use'
|
import { useAsync } from 'react-use'
|
||||||
|
import { UserAvatar } from '../../../../../common/user-avatar/user-avatar'
|
||||||
|
|
||||||
export interface RevisionListEntryProps {
|
export interface RevisionListEntryProps {
|
||||||
active: boolean
|
active: boolean
|
||||||
onSelect: () => void
|
onSelect: () => void
|
||||||
revision: RevisionMetadata
|
revision: RevisionMetadataDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,7 +47,7 @@ export const RevisionListEntry: React.FC<RevisionListEntryProps> = ({ active, on
|
||||||
try {
|
try {
|
||||||
const authorDetails = await getUserDataForRevision(revision.authorUsernames)
|
const authorDetails = await getUserDataForRevision(revision.authorUsernames)
|
||||||
return authorDetails.map((author) => (
|
return authorDetails.map((author) => (
|
||||||
<UserAvatarForUser user={author} key={author.username} showName={false} additionalClasses={'mx-1'} />
|
<UserAvatar user={author} key={author.username} showName={false} additionalClasses={'mx-1'} />
|
||||||
))
|
))
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
showErrorNotification('editor.modal.revision.errorUser')(error as Error)
|
showErrorNotification('editor.modal.revision.errorUser')(error as Error)
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { RevisionMetadata } from '../../../../../../api/revisions/types'
|
import type { RevisionMetadataDto } from '@hedgedoc/commons'
|
||||||
import { cypressId } from '../../../../../../utils/cypress-attribute'
|
import { cypressId } from '../../../../../../utils/cypress-attribute'
|
||||||
import { AsyncLoadingBoundary } from '../../../../../common/async-loading-boundary/async-loading-boundary'
|
import { AsyncLoadingBoundary } from '../../../../../common/async-loading-boundary/async-loading-boundary'
|
||||||
import { RevisionListEntry } from './revision-list-entry'
|
import { RevisionListEntry } from './revision-list-entry'
|
||||||
|
@ -13,7 +13,7 @@ import { ListGroup } from 'react-bootstrap'
|
||||||
|
|
||||||
interface RevisionListProps {
|
interface RevisionListProps {
|
||||||
selectedRevisionId?: number
|
selectedRevisionId?: number
|
||||||
revisions?: RevisionMetadata[]
|
revisions?: RevisionMetadataDto[]
|
||||||
loadingRevisions: boolean
|
loadingRevisions: boolean
|
||||||
error?: Error | boolean
|
error?: Error | boolean
|
||||||
onRevisionSelect: (selectedRevisionId: number) => void
|
onRevisionSelect: (selectedRevisionId: number) => void
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { RevisionDetails } from '../../../../../../api/revisions/types'
|
|
||||||
import { getUserInfo } from '../../../../../../api/users'
|
import { getUserInfo } from '../../../../../../api/users'
|
||||||
import type { UserInfo } from '../../../../../../api/users/types'
|
|
||||||
import { download } from '../../../../../common/download/download'
|
import { download } from '../../../../../common/download/download'
|
||||||
|
import type { RevisionDto, UserInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const DISPLAY_MAX_USERS_PER_REVISION = 9
|
const DISPLAY_MAX_USERS_PER_REVISION = 9
|
||||||
|
|
||||||
|
@ -16,7 +15,7 @@ const DISPLAY_MAX_USERS_PER_REVISION = 9
|
||||||
* @param noteId The id of the note from which to download the revision.
|
* @param noteId The id of the note from which to download the revision.
|
||||||
* @param revision The revision details object containing the content to download.
|
* @param revision The revision details object containing the content to download.
|
||||||
*/
|
*/
|
||||||
export const downloadRevision = (noteId: string, revision: RevisionDetails | null): void => {
|
export const downloadRevision = (noteId: string, revision: RevisionDto | null): void => {
|
||||||
if (!revision) {
|
if (!revision) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -30,8 +29,8 @@ export const downloadRevision = (noteId: string, revision: RevisionDetails | nul
|
||||||
* @throws {Error} in case the user-data request failed.
|
* @throws {Error} in case the user-data request failed.
|
||||||
* @return An array of user details.
|
* @return An array of user details.
|
||||||
*/
|
*/
|
||||||
export const getUserDataForRevision = async (usernames: string[]): Promise<UserInfo[]> => {
|
export const getUserDataForRevision = async (usernames: string[]): Promise<UserInfoDto[]> => {
|
||||||
const users: UserInfo[] = []
|
const users: UserInfoDto[] = []
|
||||||
const usersToFetch = Math.min(usernames.length, DISPLAY_MAX_USERS_PER_REVISION) - 1
|
const usersToFetch = Math.min(usernames.length, DISPLAY_MAX_USERS_PER_REVISION) - 1
|
||||||
for (let i = 0; i <= usersToFetch; i++) {
|
for (let i = 0; i <= usersToFetch; i++) {
|
||||||
const user = await getUserInfo(usernames[i])
|
const user = await getUserInfo(usernames[i])
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { UserAvatar } from '../../../../../common/user-avatar/user-avatar'
|
|
||||||
import { UserAvatarForUsername } from '../../../../../common/user-avatar/user-avatar-for-username'
|
import { UserAvatarForUsername } from '../../../../../common/user-avatar/user-avatar-for-username'
|
||||||
import { createCursorCssClass } from '../../../../editor-pane/codemirror-extensions/remote-cursors/create-cursor-css-class'
|
import { createCursorCssClass } from '../../../../editor-pane/codemirror-extensions/remote-cursors/create-cursor-css-class'
|
||||||
import { ActiveIndicator } from '../active-indicator'
|
import { ActiveIndicator } from '../active-indicator'
|
||||||
|
@ -12,6 +11,7 @@ import React, { useMemo } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { Incognito as IconIncognito } from 'react-bootstrap-icons'
|
import { Incognito as IconIncognito } from 'react-bootstrap-icons'
|
||||||
import { useTranslatedText } from '../../../../../../hooks/common/use-translated-text'
|
import { useTranslatedText } from '../../../../../../hooks/common/use-translated-text'
|
||||||
|
import { GuestUserAvatar } from '../../../../../common/user-avatar/guest-user-avatar'
|
||||||
|
|
||||||
export interface UserLineProps {
|
export interface UserLineProps {
|
||||||
username: string | null
|
username: string | null
|
||||||
|
@ -38,7 +38,10 @@ export const UserLine: React.FC<UserLineProps> = ({ username, displayName, activ
|
||||||
return username ? (
|
return username ? (
|
||||||
<UserAvatarForUsername username={username} additionalClasses={'flex-fill overflow-hidden px-2 text-nowrap'} />
|
<UserAvatarForUsername username={username} additionalClasses={'flex-fill overflow-hidden px-2 text-nowrap'} />
|
||||||
) : (
|
) : (
|
||||||
<UserAvatar displayName={displayName} additionalClasses={'flex-fill overflow-hidden px-2 text-nowrap'} />
|
<GuestUserAvatar
|
||||||
|
overrideDisplayName={displayName}
|
||||||
|
additionalClasses={'flex-fill overflow-hidden px-2 text-nowrap'}
|
||||||
|
/>
|
||||||
)
|
)
|
||||||
}, [displayName, username])
|
}, [displayName, username])
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -61,18 +61,56 @@ describe('Splitter', () => {
|
||||||
const view = render(<Splitter left={<>left</>} right={<>right</>} />)
|
const view = render(<Splitter left={<>left</>} right={<>right</>} />)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
const divider = await screen.findByTestId('splitter-divider')
|
const divider = await screen.findByTestId('splitter-divider')
|
||||||
|
const target: EventTarget = Mock.of<EventTarget>()
|
||||||
|
const defaultTouchEvent: Omit<Touch, 'clientX'> = {
|
||||||
|
clientY: 0,
|
||||||
|
target: target,
|
||||||
|
identifier: 0,
|
||||||
|
pageX: 0,
|
||||||
|
pageY: 0,
|
||||||
|
screenX: 0,
|
||||||
|
screenY: 0,
|
||||||
|
force: 0,
|
||||||
|
radiusX: 0,
|
||||||
|
radiusY: 0,
|
||||||
|
rotationAngle: 0
|
||||||
|
}
|
||||||
|
|
||||||
fireEvent.touchStart(divider, {})
|
fireEvent.touchStart(divider, {})
|
||||||
fireEvent.touchMove(window, Mock.of<TouchEvent>({ touches: [{ clientX: 1920 }, { clientX: 200 }] }))
|
fireEvent.touchMove(
|
||||||
|
window,
|
||||||
|
Mock.of<TouchEvent>({
|
||||||
|
touches: [
|
||||||
|
{ ...defaultTouchEvent, clientX: 1920 },
|
||||||
|
{ ...defaultTouchEvent, clientX: 200 }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
fireEvent.touchEnd(window)
|
fireEvent.touchEnd(window)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
|
|
||||||
fireEvent.touchStart(divider, {})
|
fireEvent.touchStart(divider, {})
|
||||||
fireEvent.touchMove(window, Mock.of<TouchEvent>({ touches: [{ clientX: 0 }, { clientX: 100 }] }))
|
fireEvent.touchMove(
|
||||||
|
window,
|
||||||
|
Mock.of<TouchEvent>({
|
||||||
|
touches: [
|
||||||
|
{ ...defaultTouchEvent, clientX: 0 },
|
||||||
|
{ ...defaultTouchEvent, clientX: 100 }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
fireEvent.touchCancel(window)
|
fireEvent.touchCancel(window)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
|
|
||||||
fireEvent.touchMove(window, Mock.of<TouchEvent>({ touches: [{ clientX: 500 }, { clientX: 900 }] }))
|
fireEvent.touchMove(
|
||||||
|
window,
|
||||||
|
Mock.of<TouchEvent>({
|
||||||
|
touches: [
|
||||||
|
{ ...defaultTouchEvent, clientX: 500 },
|
||||||
|
{ ...defaultTouchEvent, clientX: 900 }
|
||||||
|
]
|
||||||
|
})
|
||||||
|
)
|
||||||
expect(view.container).toMatchSnapshot()
|
expect(view.container).toMatchSnapshot()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { BackendVersion } from '../../../api/config/types'
|
import type { ServerVersionDto } from '@hedgedoc/commons'
|
||||||
import links from '../../../links.json'
|
import links from '../../../links.json'
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
import { CopyableField } from '../../common/copyable/copyable-field/copyable-field'
|
import { CopyableField } from '../../common/copyable/copyable-field/copyable-field'
|
||||||
|
@ -21,7 +21,7 @@ import { Modal } from 'react-bootstrap'
|
||||||
* @param show If the modal should be shown.
|
* @param show If the modal should be shown.
|
||||||
*/
|
*/
|
||||||
export const VersionInfoModal: React.FC<CommonModalProps> = ({ onHide, show }) => {
|
export const VersionInfoModal: React.FC<CommonModalProps> = ({ onHide, show }) => {
|
||||||
const serverVersion: BackendVersion = useFrontendConfig().version
|
const serverVersion: ServerVersionDto = useFrontendConfig().version
|
||||||
const backendVersion = useMemo(() => {
|
const backendVersion = useMemo(() => {
|
||||||
const version = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`
|
const version = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -7,13 +7,13 @@ import { useApplicationState } from '../../../hooks/common/use-application-state
|
||||||
import { useOutlineButtonVariant } from '../../../hooks/dark-mode/use-outline-button-variant'
|
import { useOutlineButtonVariant } from '../../../hooks/dark-mode/use-outline-button-variant'
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
import { UiIcon } from '../../common/icons/ui-icon'
|
import { UiIcon } from '../../common/icons/ui-icon'
|
||||||
import { UserAvatarForUser } from '../../common/user-avatar/user-avatar-for-user'
|
|
||||||
import { SignOutDropdownButton } from './sign-out-dropdown-button'
|
import { SignOutDropdownButton } from './sign-out-dropdown-button'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Dropdown } from 'react-bootstrap'
|
import { Dropdown } from 'react-bootstrap'
|
||||||
import { Person as IconPerson } from 'react-bootstrap-icons'
|
import { Person as IconPerson } from 'react-bootstrap-icons'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import { UserAvatar } from '../../common/user-avatar/user-avatar'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders a dropdown menu with user-relevant actions.
|
* Renders a dropdown menu with user-relevant actions.
|
||||||
|
@ -34,7 +34,7 @@ export const UserDropdown: React.FC = () => {
|
||||||
size='sm'
|
size='sm'
|
||||||
variant={buttonVariant}
|
variant={buttonVariant}
|
||||||
className={'d-flex align-items-center'}>
|
className={'d-flex align-items-center'}>
|
||||||
<UserAvatarForUser user={user} />
|
<UserAvatar user={user} />
|
||||||
</Dropdown.Toggle>
|
</Dropdown.Toggle>
|
||||||
|
|
||||||
<Dropdown.Menu className='text-start'>
|
<Dropdown.Menu className='text-start'>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -28,13 +28,13 @@ export const LegalSubmenu: React.FC = (): null | ReactElement => {
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<Dropdown.Divider />
|
<Dropdown.Divider />
|
||||||
<DropdownHeader i18nKey={'appbar.help.legal.header'} />
|
<DropdownHeader i18nKey={'appbar.help.legal.header'} />
|
||||||
{specialUrls.privacy !== undefined && (
|
{specialUrls.privacy !== null && (
|
||||||
<TranslatedDropdownItem href={specialUrls.privacy} i18nKey={'appbar.help.legal.privacy'} />
|
<TranslatedDropdownItem href={specialUrls.privacy} i18nKey={'appbar.help.legal.privacy'} />
|
||||||
)}
|
)}
|
||||||
{specialUrls.termsOfUse !== undefined && (
|
{specialUrls.termsOfUse !== null && (
|
||||||
<TranslatedDropdownItem href={specialUrls.termsOfUse} i18nKey={'appbar.help.legal.termsOfUse'} />
|
<TranslatedDropdownItem href={specialUrls.termsOfUse} i18nKey={'appbar.help.legal.termsOfUse'} />
|
||||||
)}
|
)}
|
||||||
{specialUrls.imprint !== undefined && (
|
{specialUrls.imprint !== null && (
|
||||||
<TranslatedDropdownItem href={specialUrls.imprint} i18nKey={'appbar.help.legal.imprint'} />
|
<TranslatedDropdownItem href={specialUrls.imprint} i18nKey={'appbar.help.legal.imprint'} />
|
||||||
)}
|
)}
|
||||||
</Fragment>
|
</Fragment>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -10,7 +10,7 @@ import { NewNoteButton } from '../../common/new-note-button/new-note-button'
|
||||||
import { HistoryButton } from '../../layout/app-bar/app-bar-elements/help-dropdown/history-button'
|
import { HistoryButton } from '../../layout/app-bar/app-bar-elements/help-dropdown/history-button'
|
||||||
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { GuestAccessLevel } from '../../../api/config/types'
|
import { GuestAccess } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Renders the card with the options for not logged-in users.
|
* Renders the card with the options for not logged-in users.
|
||||||
|
@ -20,7 +20,7 @@ export const GuestCard: React.FC = () => {
|
||||||
|
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
if (guestAccessLevel === GuestAccessLevel.DENY) {
|
if (guestAccessLevel === GuestAccess.DENY) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ export const GuestCard: React.FC = () => {
|
||||||
<NewNoteButton />
|
<NewNoteButton />
|
||||||
<HistoryButton />
|
<HistoryButton />
|
||||||
</div>
|
</div>
|
||||||
{guestAccessLevel !== GuestAccessLevel.CREATE && (
|
{guestAccessLevel !== GuestAccess.CREATE && (
|
||||||
<div className={'text-muted mt-2 small'}>
|
<div className={'text-muted mt-2 small'}>
|
||||||
<Trans i18nKey={'login.guest.noteCreationDisabled'} />
|
<Trans i18nKey={'login.guest.noteCreationDisabled'} />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useMemo } from 'react'
|
import React, { Fragment, useMemo } from 'react'
|
||||||
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
||||||
import type { AuthProviderWithCustomName } from '../../../api/config/types'
|
import type { AuthProviderWithCustomNameDto } from '@hedgedoc/commons'
|
||||||
import { AuthProviderType } from '../../../api/config/types'
|
import { ProviderType } from '@hedgedoc/commons'
|
||||||
import { LdapLoginCard } from './ldap-login-card'
|
import { LdapLoginCard } from './ldap-login-card'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,9 +18,9 @@ export const LdapLoginCards: React.FC = () => {
|
||||||
|
|
||||||
const ldapProviders = useMemo(() => {
|
const ldapProviders = useMemo(() => {
|
||||||
return authProviders
|
return authProviders
|
||||||
.filter((provider) => provider.type === AuthProviderType.LDAP)
|
.filter((provider) => provider.type === ProviderType.LDAP)
|
||||||
.map((provider) => {
|
.map((provider) => {
|
||||||
const ldapProvider = provider as AuthProviderWithCustomName
|
const ldapProvider = provider as AuthProviderWithCustomNameDto
|
||||||
return (
|
return (
|
||||||
<LdapLoginCard
|
<LdapLoginCard
|
||||||
providerName={ldapProvider.providerName}
|
providerName={ldapProvider.providerName}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -7,7 +7,7 @@
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { Card } from 'react-bootstrap'
|
import { Card } from 'react-bootstrap'
|
||||||
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
||||||
import { AuthProviderType } from '../../../api/config/types'
|
import { ProviderType } from '@hedgedoc/commons'
|
||||||
import { LocalLoginCardBody } from './local-login-card-body'
|
import { LocalLoginCardBody } from './local-login-card-body'
|
||||||
import { LocalRegisterCardBody } from './register/local-register-card-body'
|
import { LocalRegisterCardBody } from './register/local-register-card-body'
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ export const LocalLoginCard: React.FC = () => {
|
||||||
const frontendConfig = useFrontendConfig()
|
const frontendConfig = useFrontendConfig()
|
||||||
|
|
||||||
const localLoginEnabled = useMemo(() => {
|
const localLoginEnabled = useMemo(() => {
|
||||||
return frontendConfig.authProviders.some((provider) => provider.type === AuthProviderType.LOCAL)
|
return frontendConfig.authProviders.some((provider) => provider.type === ProviderType.LOCAL)
|
||||||
}, [frontendConfig])
|
}, [frontendConfig])
|
||||||
|
|
||||||
if (!localLoginEnabled) {
|
if (!localLoginEnabled) {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -40,10 +40,15 @@ export const NewUserCard: React.FC = () => {
|
||||||
const submitUserdata = useCallback(
|
const submitUserdata = useCallback(
|
||||||
(event: FormEvent) => {
|
(event: FormEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
|
let profilePicture: string | null = null
|
||||||
|
if (pictureChoice === ProfilePictureChoice.PROVIDER && value) {
|
||||||
|
profilePicture = value.photoUrl
|
||||||
|
}
|
||||||
|
|
||||||
confirmPendingUser({
|
confirmPendingUser({
|
||||||
username,
|
username,
|
||||||
displayName,
|
displayName,
|
||||||
profilePicture: pictureChoice === ProfilePictureChoice.PROVIDER ? value?.photoUrl : undefined
|
profilePicture
|
||||||
})
|
})
|
||||||
.then(() => fetchAndSetUser())
|
.then(() => fetchAndSetUser())
|
||||||
.then(() => {
|
.then(() => {
|
||||||
|
@ -51,7 +56,7 @@ export const NewUserCard: React.FC = () => {
|
||||||
})
|
})
|
||||||
.catch(showErrorNotification('login.welcome.error'))
|
.catch(showErrorNotification('login.welcome.error'))
|
||||||
},
|
},
|
||||||
[username, displayName, pictureChoice, router, showErrorNotification, value?.photoUrl]
|
[pictureChoice, value, username, displayName, showErrorNotification, router]
|
||||||
)
|
)
|
||||||
|
|
||||||
const cancelUserCreation = useCallback(() => {
|
const cancelUserCreation = useCallback(() => {
|
||||||
|
@ -111,7 +116,7 @@ export const NewUserCard: React.FC = () => {
|
||||||
<ProfilePictureSelectField
|
<ProfilePictureSelectField
|
||||||
onChange={setPictureChoice}
|
onChange={setPictureChoice}
|
||||||
value={pictureChoice}
|
value={pictureChoice}
|
||||||
pictureUrl={value?.photoUrl}
|
photoUrl={value?.photoUrl ?? null}
|
||||||
username={username}
|
username={username}
|
||||||
/>
|
/>
|
||||||
<div className={'d-flex gap-3'}>
|
<div className={'d-flex gap-3'}>
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -16,8 +16,8 @@ import {
|
||||||
Mastodon as IconMastodon
|
Mastodon as IconMastodon
|
||||||
} from 'react-bootstrap-icons'
|
} from 'react-bootstrap-icons'
|
||||||
import { Logger } from '../../../utils/logger'
|
import { Logger } from '../../../utils/logger'
|
||||||
import type { AuthProvider } from '../../../api/config/types'
|
import type { AuthProviderDto } from '@hedgedoc/commons'
|
||||||
import { AuthProviderType } from '../../../api/config/types'
|
import { ProviderType } from '@hedgedoc/commons'
|
||||||
import { IconGitlab } from '../../common/icons/additional/icon-gitlab'
|
import { IconGitlab } from '../../common/icons/additional/icon-gitlab'
|
||||||
import styles from './one-click-login-button.module.scss'
|
import styles from './one-click-login-button.module.scss'
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ const logger = new Logger('GetOneClickProviderMetadata')
|
||||||
* @param provider The provider for which to retrieve the metadata.
|
* @param provider The provider for which to retrieve the metadata.
|
||||||
* @return Name, icon, URL and CSS class of the given provider for rendering a login button.
|
* @return Name, icon, URL and CSS class of the given provider for rendering a login button.
|
||||||
*/
|
*/
|
||||||
export const getOneClickProviderMetadata = (provider: AuthProvider): OneClickMetadata => {
|
export const getOneClickProviderMetadata = (provider: AuthProviderDto): OneClickMetadata => {
|
||||||
if (provider.type !== AuthProviderType.OIDC) {
|
if (provider.type !== ProviderType.OIDC) {
|
||||||
logger.warn('Metadata for one-click-provider does not exist', provider)
|
logger.warn('Metadata for one-click-provider does not exist', provider)
|
||||||
return {
|
return {
|
||||||
name: '',
|
name: '',
|
||||||
|
|
|
@ -1,15 +1,15 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { AuthProvider, AuthProviderWithCustomName } from '../../../api/config/types'
|
import type { AuthProviderDto, AuthProviderWithCustomNameDto } from '@hedgedoc/commons'
|
||||||
import { IconButton } from '../../common/icon-button/icon-button'
|
import { IconButton } from '../../common/icon-button/icon-button'
|
||||||
import React, { useMemo } from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { getOneClickProviderMetadata } from './get-one-click-provider-metadata'
|
import { getOneClickProviderMetadata } from './get-one-click-provider-metadata'
|
||||||
|
|
||||||
export interface ViaOneClickProps {
|
export interface ViaOneClickProps {
|
||||||
provider: AuthProvider
|
provider: AuthProviderDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -19,7 +19,7 @@ export interface ViaOneClickProps {
|
||||||
*/
|
*/
|
||||||
export const OneClickLoginButton: React.FC<ViaOneClickProps> = ({ provider }) => {
|
export const OneClickLoginButton: React.FC<ViaOneClickProps> = ({ provider }) => {
|
||||||
const { className, icon, url, name } = useMemo(() => getOneClickProviderMetadata(provider), [provider])
|
const { className, icon, url, name } = useMemo(() => getOneClickProviderMetadata(provider), [provider])
|
||||||
const text = (provider as AuthProviderWithCustomName).providerName || name
|
const text = (provider as AuthProviderWithCustomNameDto).providerName || name
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<IconButton className={className} icon={icon} href={url} title={text} border={true}>
|
<IconButton className={className} icon={icon} href={url} title={text} border={true}>
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import type { AuthProvider } from '../../../api/config/types'
|
import type { AuthProviderDto } from '@hedgedoc/commons'
|
||||||
import { authProviderTypeOneClick } from '../../../api/config/types'
|
import { ProviderType } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters the given auth providers to one-click providers only.
|
* Filters the given auth providers to one-click providers only.
|
||||||
* @param authProviders The auth providers to filter
|
* @param authProviders The auth providers to filter
|
||||||
* @return only one click auth providers
|
* @return only one click auth providers
|
||||||
*/
|
*/
|
||||||
export const filterOneClickProviders = (authProviders: AuthProvider[]) => {
|
export const filterOneClickProviders = (authProviders: AuthProviderDto[]) => {
|
||||||
return authProviders.filter((provider: AuthProvider): boolean => authProviderTypeOneClick.includes(provider.type))
|
return authProviders.filter((provider: AuthProviderDto): boolean => provider.type === ProviderType.OIDC)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { AccessTokenWithSecret } from '../../../api/tokens/types'
|
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
import { CopyableField } from '../../common/copyable/copyable-field/copyable-field'
|
import { CopyableField } from '../../common/copyable/copyable-field/copyable-field'
|
||||||
import type { ModalVisibilityProps } from '../../common/modals/common-modal'
|
import type { ModalVisibilityProps } from '../../common/modals/common-modal'
|
||||||
|
@ -11,9 +10,10 @@ import { CommonModal } from '../../common/modals/common-modal'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Button, Modal } from 'react-bootstrap'
|
import { Button, Modal } from 'react-bootstrap'
|
||||||
import { Trans } from 'react-i18next'
|
import { Trans } from 'react-i18next'
|
||||||
|
import type { ApiTokenWithSecretDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export interface AccessTokenCreatedModalProps extends ModalVisibilityProps {
|
export interface AccessTokenCreatedModalProps extends ModalVisibilityProps {
|
||||||
tokenWithSecret?: AccessTokenWithSecret
|
tokenWithSecret?: ApiTokenWithSecretDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { AccessTokenWithSecret } from '../../../../api/tokens/types'
|
|
||||||
import { AccessTokenCreatedModal } from '../access-token-created-modal'
|
import { AccessTokenCreatedModal } from '../access-token-created-modal'
|
||||||
import type { AccessTokenUpdateProps } from '../profile-access-tokens'
|
import type { AccessTokenUpdateProps } from '../profile-access-tokens'
|
||||||
import { AccessTokenCreationFormExpiryField } from './access-token-creation-form-expiry-field'
|
import { AccessTokenCreationFormExpiryField } from './access-token-creation-form-expiry-field'
|
||||||
|
@ -15,6 +14,7 @@ import type { ChangeEvent } from 'react'
|
||||||
import React, { Fragment, useCallback, useMemo, useState } from 'react'
|
import React, { Fragment, useCallback, useMemo, useState } from 'react'
|
||||||
import { Form } from 'react-bootstrap'
|
import { Form } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import type { ApiTokenWithSecretDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
interface NewTokenFormValues {
|
interface NewTokenFormValues {
|
||||||
label: string
|
label: string
|
||||||
|
@ -38,7 +38,7 @@ export const AccessTokenCreationForm: React.FC<AccessTokenUpdateProps> = ({ onUp
|
||||||
}, [expiryDates])
|
}, [expiryDates])
|
||||||
|
|
||||||
const [formValues, setFormValues] = useState<NewTokenFormValues>(() => formValuesInitialState)
|
const [formValues, setFormValues] = useState<NewTokenFormValues>(() => formValuesInitialState)
|
||||||
const [newTokenWithSecret, setNewTokenWithSecret] = useState<AccessTokenWithSecret>()
|
const [newTokenWithSecret, setNewTokenWithSecret] = useState<ApiTokenWithSecretDto>()
|
||||||
|
|
||||||
const onHideCreatedModal = useCallback(() => {
|
const onHideCreatedModal = useCallback(() => {
|
||||||
setFormValues(formValuesInitialState)
|
setFormValues(formValuesInitialState)
|
||||||
|
|
|
@ -1,40 +1,40 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { postNewAccessToken } from '../../../../../api/tokens'
|
import { postNewAccessToken } from '../../../../../api/api-tokens'
|
||||||
import type { AccessTokenWithSecret } from '../../../../../api/tokens/types'
|
|
||||||
import { useUiNotifications } from '../../../../notifications/ui-notification-boundary'
|
import { useUiNotifications } from '../../../../notifications/ui-notification-boundary'
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
import type { FormEvent } from 'react'
|
import type { FormEvent } from 'react'
|
||||||
import { useCallback } from 'react'
|
import { useCallback } from 'react'
|
||||||
|
import type { ApiTokenWithSecretDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Callback for requesting a new access token from the API and returning the response token and secret.
|
* Callback for requesting a new access token from the API and returning the response token and secret.
|
||||||
*
|
*
|
||||||
* @param label The label for the new access token.
|
* @param label The label for the new access token.
|
||||||
* @param expiryDate The expiry date of the new access token.
|
* @param expiryDateStr The expiry date of the new access token.
|
||||||
* @param setNewTokenWithSecret Callback to set the new access token with the secret from the API.
|
* @param setNewTokenWithSecret Callback to set the new access token with the secret from the API.
|
||||||
* @return Callback that can be called when the new access token should be requested.
|
* @return Callback that can be called when the new access token should be requested.
|
||||||
*/
|
*/
|
||||||
export const useOnCreateToken = (
|
export const useOnCreateToken = (
|
||||||
label: string,
|
label: string,
|
||||||
expiryDate: string,
|
expiryDateStr: string,
|
||||||
setNewTokenWithSecret: (token: AccessTokenWithSecret) => void
|
setNewTokenWithSecret: (token: ApiTokenWithSecretDto) => void
|
||||||
): ((event: FormEvent) => void) => {
|
): ((event: FormEvent) => void) => {
|
||||||
const { showErrorNotification } = useUiNotifications()
|
const { showErrorNotification } = useUiNotifications()
|
||||||
|
|
||||||
return useCallback(
|
return useCallback(
|
||||||
(event: FormEvent) => {
|
(event: FormEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
const expiryInMillis = DateTime.fromFormat(expiryDate, 'yyyy-MM-dd').toMillis()
|
const expiryDate = DateTime.fromFormat(expiryDateStr, 'yyyy-MM-dd').toJSDate()
|
||||||
postNewAccessToken(label, expiryInMillis)
|
postNewAccessToken(label, expiryDate)
|
||||||
.then((tokenWithSecret) => {
|
.then((tokenWithSecret) => {
|
||||||
setNewTokenWithSecret(tokenWithSecret)
|
setNewTokenWithSecret(tokenWithSecret)
|
||||||
})
|
})
|
||||||
.catch(showErrorNotification('profile.accessTokens.creationFailed'))
|
.catch(showErrorNotification('profile.accessTokens.creationFailed'))
|
||||||
},
|
},
|
||||||
[expiryDate, label, setNewTokenWithSecret, showErrorNotification]
|
[expiryDateStr, label, setNewTokenWithSecret, showErrorNotification]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { deleteAccessToken } from '../../../api/tokens'
|
import { deleteAccessToken } from '../../../api/api-tokens'
|
||||||
import type { AccessToken } from '../../../api/tokens/types'
|
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
import type { ModalVisibilityProps } from '../../common/modals/common-modal'
|
import type { ModalVisibilityProps } from '../../common/modals/common-modal'
|
||||||
import { CommonModal } from '../../common/modals/common-modal'
|
import { CommonModal } from '../../common/modals/common-modal'
|
||||||
|
@ -12,9 +11,10 @@ import { useUiNotifications } from '../../notifications/ui-notification-boundary
|
||||||
import React, { useCallback } from 'react'
|
import React, { useCallback } from 'react'
|
||||||
import { Button, Modal } from 'react-bootstrap'
|
import { Button, Modal } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import type { ApiTokenDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export interface AccessTokenDeletionModalProps extends ModalVisibilityProps {
|
export interface AccessTokenDeletionModalProps extends ModalVisibilityProps {
|
||||||
token: AccessToken
|
token: ApiTokenDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,9 +1,8 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { AccessToken } from '../../../api/tokens/types'
|
|
||||||
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
import { useBooleanState } from '../../../hooks/common/use-boolean-state'
|
||||||
import { cypressId } from '../../../utils/cypress-attribute'
|
import { cypressId } from '../../../utils/cypress-attribute'
|
||||||
import { IconButton } from '../../common/icon-button/icon-button'
|
import { IconButton } from '../../common/icon-button/icon-button'
|
||||||
|
@ -14,9 +13,10 @@ import React, { useCallback, useMemo } from 'react'
|
||||||
import { Col, ListGroup, Row } from 'react-bootstrap'
|
import { Col, ListGroup, Row } from 'react-bootstrap'
|
||||||
import { Trash as IconTrash } from 'react-bootstrap-icons'
|
import { Trash as IconTrash } from 'react-bootstrap-icons'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import type { ApiTokenDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export interface AccessTokenListEntryProps {
|
export interface AccessTokenListEntryProps {
|
||||||
token: AccessToken
|
token: ApiTokenDto
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { getAccessTokenList } from '../../../api/tokens'
|
import { getAccessTokenList } from '../../../api/api-tokens'
|
||||||
import type { AccessToken } from '../../../api/tokens/types'
|
|
||||||
import { useUiNotifications } from '../../notifications/ui-notification-boundary'
|
import { useUiNotifications } from '../../notifications/ui-notification-boundary'
|
||||||
import { AccessTokenCreationForm } from './access-token-creation-form/access-token-creation-form'
|
import { AccessTokenCreationForm } from './access-token-creation-form/access-token-creation-form'
|
||||||
import { AccessTokenListEntry } from './access-token-list-entry'
|
import { AccessTokenListEntry } from './access-token-list-entry'
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||||
import { Card, ListGroup } from 'react-bootstrap'
|
import { Card, ListGroup } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
|
import type { ApiTokenDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
export interface AccessTokenUpdateProps {
|
export interface AccessTokenUpdateProps {
|
||||||
onUpdateList: () => void
|
onUpdateList: () => void
|
||||||
|
@ -21,7 +21,7 @@ export interface AccessTokenUpdateProps {
|
||||||
*/
|
*/
|
||||||
export const ProfileAccessTokens: React.FC = () => {
|
export const ProfileAccessTokens: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
const [accessTokens, setAccessTokens] = useState<AccessToken[]>([])
|
const [accessTokens, setAccessTokens] = useState<ApiTokenDto[]>([])
|
||||||
const { showErrorNotification } = useUiNotifications()
|
const { showErrorNotification } = useUiNotifications()
|
||||||
|
|
||||||
const refreshAccessTokens = useCallback(() => {
|
const refreshAccessTokens = useCallback(() => {
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { updateDisplayName } from '../../../api/me'
|
import { updateUser } from '../../../api/me'
|
||||||
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
import { useApplicationState } from '../../../hooks/common/use-application-state'
|
||||||
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
import { useOnInputChange } from '../../../hooks/common/use-on-input-change'
|
||||||
import { DisplayNameField } from '../../common/fields/display-name-field'
|
import { DisplayNameField } from '../../common/fields/display-name-field'
|
||||||
|
@ -27,7 +27,7 @@ export const ProfileDisplayName: React.FC = () => {
|
||||||
const onSubmitNameChange = useCallback(
|
const onSubmitNameChange = useCallback(
|
||||||
(event: FormEvent) => {
|
(event: FormEvent) => {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
updateDisplayName(displayName)
|
updateUser(displayName, null)
|
||||||
.then(fetchAndSetUser)
|
.then(fetchAndSetUser)
|
||||||
.catch(showErrorNotification('profile.changeDisplayNameFailed'))
|
.catch(showErrorNotification('profile.changeDisplayNameFailed'))
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { FrontendConfig } from '../../api/config/types'
|
import type { FrontendConfigDto } from '@hedgedoc/commons'
|
||||||
import type { CheatsheetExtension } from '../../components/cheatsheet/cheatsheet-extension'
|
import type { CheatsheetExtension } from '../../components/cheatsheet/cheatsheet-extension'
|
||||||
import type { Linter } from '../../components/editor-page/editor-pane/linter/linter'
|
import type { Linter } from '../../components/editor-page/editor-pane/linter/linter'
|
||||||
import type { MarkdownRendererExtension } from '../../components/markdown-renderer/extensions/_base-classes/markdown-renderer-extension'
|
import type { MarkdownRendererExtension } from '../../components/markdown-renderer/extensions/_base-classes/markdown-renderer-extension'
|
||||||
|
@ -14,7 +14,7 @@ import type React from 'react'
|
||||||
import { Fragment } from 'react'
|
import { Fragment } from 'react'
|
||||||
|
|
||||||
export interface MarkdownRendererExtensionOptions {
|
export interface MarkdownRendererExtensionOptions {
|
||||||
frontendConfig: FrontendConfig
|
frontendConfig: FrontendConfigDto
|
||||||
eventEmitter: EventEmitter2
|
eventEmitter: EventEmitter2
|
||||||
rendererType: RendererType
|
rendererType: RendererType
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -21,7 +21,7 @@ import type { CompletionSource } from '@codemirror/autocomplete'
|
||||||
*/
|
*/
|
||||||
export class PlantumlAppExtension extends AppExtension {
|
export class PlantumlAppExtension extends AppExtension {
|
||||||
buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): MarkdownRendererExtension[] {
|
buildMarkdownRendererExtensions(options: MarkdownRendererExtensionOptions): MarkdownRendererExtension[] {
|
||||||
return [new PlantumlMarkdownExtension(options.frontendConfig.plantumlServer)]
|
return [new PlantumlMarkdownExtension(options.frontendConfig.plantUmlServer)]
|
||||||
}
|
}
|
||||||
|
|
||||||
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
buildCheatsheetExtensions(): CheatsheetExtension[] {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -33,7 +33,7 @@ describe('PlantUML markdown extensions', () => {
|
||||||
it('renders an error if no server is defined', () => {
|
it('renders an error if no server is defined', () => {
|
||||||
const view = render(
|
const view = render(
|
||||||
<TestMarkdownRenderer
|
<TestMarkdownRenderer
|
||||||
extensions={[new PlantumlMarkdownExtension(undefined)]}
|
extensions={[new PlantumlMarkdownExtension(null)]}
|
||||||
content={'```plantuml\nclass Example\n```'}
|
content={'```plantuml\nclass Example\n```'}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -19,7 +19,7 @@ import type Token from 'markdown-it/lib/token'
|
||||||
* @see https://plantuml.com
|
* @see https://plantuml.com
|
||||||
*/
|
*/
|
||||||
export class PlantumlMarkdownExtension extends MarkdownRendererExtension {
|
export class PlantumlMarkdownExtension extends MarkdownRendererExtension {
|
||||||
constructor(private plantumlServerUrl: string | undefined) {
|
constructor(private plantumlServerUrl: string | null) {
|
||||||
super()
|
super()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -30,8 +30,8 @@ export const respondToMatchingRequest = <T>(
|
||||||
req: NextApiRequest,
|
req: NextApiRequest,
|
||||||
res: NextApiResponse,
|
res: NextApiResponse,
|
||||||
response: T,
|
response: T,
|
||||||
statusCode = 200,
|
statusCode: number = 200,
|
||||||
respondMethodNotAllowedOnMismatch = true
|
respondMethodNotAllowedOnMismatch: boolean = true
|
||||||
): boolean => {
|
): boolean => {
|
||||||
if (!isMockMode) {
|
if (!isMockMode) {
|
||||||
res.status(404).send('Mock API is disabled')
|
res.status(404).send('Mock API is disabled')
|
||||||
|
|
|
@ -1,27 +1,25 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import { useTranslatedText } from './use-translated-text'
|
import { useTranslatedText } from './use-translated-text'
|
||||||
import { renderHook } from '@testing-library/react'
|
import { renderHook } from '@testing-library/react'
|
||||||
import type { Namespace } from 'i18next'
|
|
||||||
import * as ReactI18NextModule from 'react-i18next'
|
import * as ReactI18NextModule from 'react-i18next'
|
||||||
import type { UseTranslationResponse } from 'react-i18next'
|
|
||||||
import { Mock } from 'ts-mockery'
|
import { Mock } from 'ts-mockery'
|
||||||
|
import type { UseTranslationResponse } from 'react-i18next'
|
||||||
|
|
||||||
jest.mock('react-i18next')
|
jest.mock('react-i18next')
|
||||||
|
|
||||||
describe('useTranslatedText', () => {
|
describe('useTranslatedText', () => {
|
||||||
const mockTranslation = 'mockTranslation'
|
const mockTranslation = 'mockTranslation'
|
||||||
const mockKey = 'mockKey'
|
const mockKey = 'mockKey'
|
||||||
let translateFunction: jest.Mock
|
const translateFunction = jest.fn(() => mockTranslation)
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
translateFunction = jest.fn(() => mockTranslation)
|
const useTranslateMock = Mock.of({
|
||||||
const useTranslateMock = Mock.of<UseTranslationResponse<Namespace, unknown>>({
|
|
||||||
t: translateFunction
|
t: translateFunction
|
||||||
})
|
}) as unknown as UseTranslationResponse<never, never>
|
||||||
jest.spyOn(ReactI18NextModule, 'useTranslation').mockReturnValue(useTranslateMock)
|
jest.spyOn(ReactI18NextModule, 'useTranslation').mockReturnValue(useTranslateMock)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { FrontendConfig } from '../../../api/config/types'
|
import type { FrontendConfigDto } from '@hedgedoc/commons'
|
||||||
import { AuthProviderType, GuestAccessLevel } from '../../../api/config/types'
|
import { ProviderType, GuestAccess } from '@hedgedoc/commons'
|
||||||
import {
|
import {
|
||||||
HttpMethod,
|
HttpMethod,
|
||||||
respondToMatchingRequest,
|
respondToMatchingRequest,
|
||||||
|
@ -13,7 +13,7 @@ import {
|
||||||
import { isTestMode } from '../../../utils/test-modes'
|
import { isTestMode } from '../../../utils/test-modes'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
const initialConfig: FrontendConfig = {
|
const initialConfig: FrontendConfigDto = {
|
||||||
allowRegister: true,
|
allowRegister: true,
|
||||||
allowProfileEdits: true,
|
allowProfileEdits: true,
|
||||||
allowChooseUsername: true,
|
allowChooseUsername: true,
|
||||||
|
@ -21,7 +21,7 @@ const initialConfig: FrontendConfig = {
|
||||||
name: 'DEMO Corp',
|
name: 'DEMO Corp',
|
||||||
logo: '/public/img/demo.png'
|
logo: '/public/img/demo.png'
|
||||||
},
|
},
|
||||||
guestAccess: GuestAccessLevel.WRITE,
|
guestAccess: GuestAccess.WRITE,
|
||||||
useImageProxy: false,
|
useImageProxy: false,
|
||||||
specialUrls: {
|
specialUrls: {
|
||||||
privacy: 'https://example.com/privacy',
|
privacy: 'https://example.com/privacy',
|
||||||
|
@ -33,31 +33,34 @@ const initialConfig: FrontendConfig = {
|
||||||
minor: 0,
|
minor: 0,
|
||||||
patch: 0,
|
patch: 0,
|
||||||
preRelease: isTestMode ? undefined : '',
|
preRelease: isTestMode ? undefined : '',
|
||||||
commit: 'mock'
|
commit: 'mock',
|
||||||
|
fullString: `${isTestMode ? 0 : 2}.0.0`
|
||||||
},
|
},
|
||||||
plantumlServer: isTestMode ? 'http://mock-plantuml.local' : 'https://www.plantuml.com/plantuml',
|
plantUmlServer: isTestMode ? 'http://mock-plantuml.local' : 'https://www.plantuml.com/plantuml',
|
||||||
maxDocumentLength: isTestMode ? 200 : 1000000,
|
maxDocumentLength: isTestMode ? 200 : 1000000,
|
||||||
authProviders: [
|
authProviders: [
|
||||||
{
|
{
|
||||||
type: AuthProviderType.LOCAL
|
type: ProviderType.LOCAL
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AuthProviderType.LDAP,
|
type: ProviderType.LDAP,
|
||||||
identifier: 'test-ldap',
|
identifier: 'test-ldap',
|
||||||
providerName: 'Test LDAP'
|
providerName: 'Test LDAP',
|
||||||
|
theme: null
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
type: AuthProviderType.OIDC,
|
type: ProviderType.OIDC,
|
||||||
identifier: 'test-oidc',
|
identifier: 'test-oidc',
|
||||||
providerName: 'Test OIDC'
|
providerName: 'Test OIDC',
|
||||||
|
theme: null
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
let currentConfig: FrontendConfig = initialConfig
|
let currentConfig: FrontendConfigDto = initialConfig
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
const responseSuccessful = respondToMatchingRequest<FrontendConfig>(
|
const responseSuccessful = respondToMatchingRequest<FrontendConfigDto>(
|
||||||
HttpMethod.GET,
|
HttpMethod.GET,
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
@ -66,10 +69,10 @@ const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
if (!responseSuccessful) {
|
if (!responseSuccessful) {
|
||||||
respondToTestRequest<FrontendConfig>(req, res, () => {
|
respondToTestRequest<FrontendConfigDto>(req, res, () => {
|
||||||
currentConfig = {
|
currentConfig = {
|
||||||
...initialConfig,
|
...initialConfig,
|
||||||
...(req.body as FrontendConfig)
|
...(req.body as FrontendConfigDto)
|
||||||
}
|
}
|
||||||
return currentConfig
|
return currentConfig
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { GroupInfo } from '../../../../api/group/types'
|
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { GroupInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
respondToMatchingRequest<GroupInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<GroupInfoDto>(HttpMethod.GET, req, res, {
|
||||||
name: '_EVERYONE',
|
name: '_EVERYONE',
|
||||||
displayName: 'Everyone',
|
displayName: 'Everyone',
|
||||||
special: true
|
special: true
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { GroupInfo } from '../../../../api/group/types'
|
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { GroupInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
respondToMatchingRequest<GroupInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<GroupInfoDto>(HttpMethod.GET, req, res, {
|
||||||
name: '_LOGGED_IN',
|
name: '_LOGGED_IN',
|
||||||
displayName: 'All registered users',
|
displayName: 'All registered users',
|
||||||
special: true
|
special: true
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { GroupInfo } from '../../../../api/group/types'
|
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { GroupInfoDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
respondToMatchingRequest<GroupInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<GroupInfoDto>(HttpMethod.GET, req, res, {
|
||||||
name: 'hedgedoc-devs',
|
name: 'hedgedoc-devs',
|
||||||
displayName: 'HedgeDoc devs',
|
displayName: 'HedgeDoc devs',
|
||||||
special: true
|
special: true
|
||||||
|
|
|
@ -1,11 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { LoginUserInfo } from '../../../../api/me/types'
|
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { LoginUserInfoDto } from '@hedgedoc/commons'
|
||||||
|
import { ProviderType } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
const cookieSet = req.headers?.['cookie']?.split(';').find((value) => value.trim() === 'mock-session=1') !== undefined
|
const cookieSet = req.headers?.['cookie']?.split(';').find((value) => value.trim() === 'mock-session=1') !== undefined
|
||||||
|
@ -13,11 +14,11 @@ const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
res.status(403).json({})
|
res.status(403).json({})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
respondToMatchingRequest<LoginUserInfo>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<LoginUserInfoDto>(HttpMethod.GET, req, res, {
|
||||||
username: 'mock',
|
username: 'mock',
|
||||||
photoUrl: '/public/img/avatar.png',
|
photoUrl: '/public/img/avatar.png',
|
||||||
displayName: 'Mock User',
|
displayName: 'Mock User',
|
||||||
authProvider: 'local',
|
authProvider: ProviderType.LOCAL,
|
||||||
email: 'mock@hedgedoc.test'
|
email: 'mock@hedgedoc.test'
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { MediaUpload } from '../../../../api/media/types'
|
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../../handler-utils/respond-to-matching-request'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { MediaUploadDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
const handler = (req: NextApiRequest, res: NextApiResponse) => {
|
||||||
respondToMatchingRequest<MediaUpload[]>(HttpMethod.GET, req, res, [
|
respondToMatchingRequest<MediaUploadDto[]>(HttpMethod.GET, req, res, [
|
||||||
{
|
{
|
||||||
username: 'tilman',
|
username: 'tilman',
|
||||||
createdAt: '2022-03-20T20:36:32Z',
|
createdAt: '2022-03-20T20:36:32Z',
|
||||||
|
|
|
@ -1,12 +1,12 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { MediaUpload } from '../../../api/media/types'
|
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../handler-utils/respond-to-matching-request'
|
||||||
import { isMockMode, isTestMode } from '../../../utils/test-modes'
|
import { isMockMode, isTestMode } from '../../../utils/test-modes'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
import type { MediaUploadDto } from '@hedgedoc/commons'
|
||||||
|
|
||||||
const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
|
const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void> => {
|
||||||
if (isMockMode && !isTestMode) {
|
if (isMockMode && !isTestMode) {
|
||||||
|
@ -15,7 +15,7 @@ const handler = async (req: NextApiRequest, res: NextApiResponse): Promise<void>
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
respondToMatchingRequest<MediaUpload>(
|
respondToMatchingRequest<MediaUploadDto>(
|
||||||
HttpMethod.POST,
|
HttpMethod.POST,
|
||||||
req,
|
req,
|
||||||
res,
|
res,
|
||||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1,14 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { RevisionDetails } from '../../../../../../api/revisions/types'
|
import type { RevisionDto } from '@hedgedoc/commons'
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../../../../handler-utils/respond-to-matching-request'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
respondToMatchingRequest<RevisionDetails>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<RevisionDto>(HttpMethod.GET, req, res, {
|
||||||
id: 0,
|
id: 0,
|
||||||
createdAt: '2021-12-21T16:59:42.000Z',
|
createdAt: '2021-12-21T16:59:42.000Z',
|
||||||
title: 'Features',
|
title: 'Features',
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
/*
|
/*
|
||||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
import type { RevisionDetails } from '../../../../../../api/revisions/types'
|
import type { RevisionDto } from '@hedgedoc/commons'
|
||||||
import { HttpMethod, respondToMatchingRequest } from '../../../../../../handler-utils/respond-to-matching-request'
|
import { HttpMethod, respondToMatchingRequest } from '../../../../../../handler-utils/respond-to-matching-request'
|
||||||
import type { NextApiRequest, NextApiResponse } from 'next'
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
const handler = (req: NextApiRequest, res: NextApiResponse): void => {
|
||||||
respondToMatchingRequest<RevisionDetails>(HttpMethod.GET, req, res, {
|
respondToMatchingRequest<RevisionDto>(HttpMethod.GET, req, res, {
|
||||||
id: 1,
|
id: 1,
|
||||||
createdAt: '2021-12-29T17:54:11.000Z',
|
createdAt: '2021-12-29T17:54:11.000Z',
|
||||||
title: 'Features',
|
title: 'Features',
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue