mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-14 15:14:56 -04:00
Fix Communication between frontend and backend (#1201)
Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de> Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
4a18e51c83
commit
9cf7980334
38 changed files with 268 additions and 164 deletions
|
@ -26,7 +26,7 @@ describe('History', () => {
|
||||||
describe('Pinning', () => {
|
describe('Pinning', () => {
|
||||||
describe('working', () => {
|
describe('working', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('PUT', '/api/private/history/features', (req) => {
|
cy.intercept('PUT', '/api/private/me/history/features', (req) => {
|
||||||
req.reply(200, req.body)
|
req.reply(200, req.body)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -60,7 +60,7 @@ describe('History', () => {
|
||||||
|
|
||||||
describe('failing', () => {
|
describe('failing', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept('PUT', '/api/private/history/features', {
|
cy.intercept('PUT', '/api/private/me/history/features', {
|
||||||
statusCode: 401
|
statusCode: 401
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -45,7 +45,7 @@ export const config = {
|
||||||
saml: 'aufSAMLn.de'
|
saml: 'aufSAMLn.de'
|
||||||
},
|
},
|
||||||
maxDocumentLength: 200,
|
maxDocumentLength: 200,
|
||||||
specialLinks: {
|
specialUrls: {
|
||||||
privacy: 'https://example.com/privacy',
|
privacy: 'https://example.com/privacy',
|
||||||
termsOfUse: 'https://example.com/termsOfUse',
|
termsOfUse: 'https://example.com/termsOfUse',
|
||||||
imprint: 'https://example.com/imprint'
|
imprint: 'https://example.com/imprint'
|
||||||
|
@ -57,8 +57,8 @@ export const config = {
|
||||||
issueTrackerUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
|
issueTrackerUrl: 'https://www.youtube.com/watch?v=dQw4w9WgXcQ'
|
||||||
},
|
},
|
||||||
'iframeCommunication': {
|
'iframeCommunication': {
|
||||||
'editorOrigin': 'http://127.0.0.1:3001',
|
'editorOrigin': 'http://127.0.0.1:3001/',
|
||||||
'rendererOrigin': 'http://127.0.0.1:3001'
|
'rendererOrigin': 'http://127.0.0.1:3001/'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -18,16 +18,21 @@ Cypress.Commands.add('visitTestEditor', (query?: string) => {
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
cy.intercept(`/api/private/notes/${ testNoteId }-get`, {
|
cy.intercept(`/api/private/notes/${ testNoteId }-get`, {
|
||||||
'id': 'ABC123',
|
"content": "",
|
||||||
'alias': 'banner',
|
"metadata": {
|
||||||
'lastChange': {
|
"id": "ABC11",
|
||||||
'userId': 'test',
|
"alias": "banner",
|
||||||
'timestamp': 1600033920
|
"version": 2,
|
||||||
},
|
"viewCount": 0,
|
||||||
'viewCount': 0,
|
"updateTime": "2021-04-24T09:27:51.000Z",
|
||||||
'createTime': 1600033920,
|
"updateUser": {
|
||||||
'content': '',
|
"userName": "test",
|
||||||
'authorship': [],
|
"displayName": "Testy",
|
||||||
'preVersionTwoNote': true
|
"photo": "",
|
||||||
|
"email": ""
|
||||||
|
},
|
||||||
|
"createTime": "2021-04-24T09:27:51.000Z",
|
||||||
|
"editedBy": []
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
@ -30,18 +30,19 @@
|
||||||
"maxDocumentLength": 100000,
|
"maxDocumentLength": 100000,
|
||||||
"useImageProxy": false,
|
"useImageProxy": false,
|
||||||
"plantumlServer": "https://www.plantuml.com/plantuml",
|
"plantumlServer": "https://www.plantuml.com/plantuml",
|
||||||
"specialLinks": {
|
"specialUrls": {
|
||||||
"privacy": "https://example.com/privacy",
|
"privacy": "https://example.com/privacy",
|
||||||
"termsOfUse": "https://example.com/termsOfUse",
|
"termsOfUse": "https://example.com/termsOfUse",
|
||||||
"imprint": "https://example.com/imprint"
|
"imprint": "https://example.com/imprint"
|
||||||
},
|
},
|
||||||
"version": {
|
"version": {
|
||||||
"version": "mock",
|
"major": -1,
|
||||||
"sourceCodeUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
"minor": -1,
|
||||||
"issueTrackerUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
"patch": -1,
|
||||||
|
"commit": "mock"
|
||||||
},
|
},
|
||||||
"iframeCommunication": {
|
"iframeCommunication": {
|
||||||
"editorOrigin": "http://localhost:3001",
|
"editorOrigin": "http://localhost:3001/",
|
||||||
"rendererOrigin": "http://localhost:3001"
|
"rendererOrigin": "http://localhost:3001/"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"id": "ABC123",
|
|
||||||
"alias": "banner",
|
|
||||||
"lastChange": {
|
|
||||||
"userId": "test",
|
|
||||||
"timestamp": 1600033920
|
|
||||||
},
|
|
||||||
"viewcount": 0,
|
|
||||||
"createtime": 1600033920,
|
|
||||||
"content": "This is the test banner text",
|
|
||||||
"authorship": [],
|
|
||||||
"preVersionTwoNote": true
|
|
||||||
}
|
|
18
public/api/private/notes/banner-get
Normal file
18
public/api/private/notes/banner-get
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"content": "This is the test banner text",
|
||||||
|
"metadata": {
|
||||||
|
"id": "ABC11",
|
||||||
|
"alias": "banner",
|
||||||
|
"version": 2,
|
||||||
|
"viewCount": 0,
|
||||||
|
"updateTime": "2021-04-24T09:27:51.000Z",
|
||||||
|
"updateUser": {
|
||||||
|
"userName": "test",
|
||||||
|
"displayName": "Testy",
|
||||||
|
"photo": "",
|
||||||
|
"email": ""
|
||||||
|
},
|
||||||
|
"createTime": "2021-04-24T09:27:51.000Z",
|
||||||
|
"editedBy": []
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
|
@ -1,13 +0,0 @@
|
||||||
{
|
|
||||||
"id": "ABC123",
|
|
||||||
"alias": "old",
|
|
||||||
"lastChange": {
|
|
||||||
"userId": "test",
|
|
||||||
"timestamp": 1600033920
|
|
||||||
},
|
|
||||||
"viewcount": 0,
|
|
||||||
"createtime": 1600033920,
|
|
||||||
"content": "test123",
|
|
||||||
"authorship": [],
|
|
||||||
"preVersionTwoNote": false
|
|
||||||
}
|
|
18
public/api/private/notes/old-get
Normal file
18
public/api/private/notes/old-get
Normal file
|
@ -0,0 +1,18 @@
|
||||||
|
{
|
||||||
|
"content": "test123",
|
||||||
|
"metadata": {
|
||||||
|
"id": "ABC3",
|
||||||
|
"alias": "old",
|
||||||
|
"version": 1,
|
||||||
|
"viewCount": 0,
|
||||||
|
"updateTime": "2021-04-24T09:27:51.000Z",
|
||||||
|
"updateUser": {
|
||||||
|
"userName": "test",
|
||||||
|
"displayName": "Testy",
|
||||||
|
"photo": "",
|
||||||
|
"email": ""
|
||||||
|
},
|
||||||
|
"createTime": "2021-04-24T09:27:51.000Z",
|
||||||
|
"editedBy": []
|
||||||
|
}
|
||||||
|
}
|
12
src/api/config/types.d.ts
vendored
12
src/api/config/types.d.ts
vendored
|
@ -12,7 +12,7 @@ export interface Config {
|
||||||
banner: BannerConfig,
|
banner: BannerConfig,
|
||||||
customAuthNames: CustomAuthNames,
|
customAuthNames: CustomAuthNames,
|
||||||
useImageProxy: boolean,
|
useImageProxy: boolean,
|
||||||
specialLinks: SpecialLinks,
|
specialUrls: SpecialUrls,
|
||||||
version: BackendVersion,
|
version: BackendVersion,
|
||||||
plantumlServer: string | null,
|
plantumlServer: string | null,
|
||||||
maxDocumentLength: number,
|
maxDocumentLength: number,
|
||||||
|
@ -35,9 +35,11 @@ export interface BannerConfig {
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BackendVersion {
|
export interface BackendVersion {
|
||||||
version: string,
|
major: number
|
||||||
sourceCodeUrl: string
|
minor: number
|
||||||
issueTrackerUrl: string
|
patch: number
|
||||||
|
preRelease?: string
|
||||||
|
commit?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AuthProvidersState {
|
export interface AuthProvidersState {
|
||||||
|
@ -60,7 +62,7 @@ export interface CustomAuthNames {
|
||||||
saml: string;
|
saml: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SpecialLinks {
|
export interface SpecialUrls {
|
||||||
privacy: string,
|
privacy: string,
|
||||||
termsOfUse: string,
|
termsOfUse: string,
|
||||||
imprint: string,
|
imprint: string,
|
||||||
|
|
11
src/api/group/types.d.ts
vendored
Normal file
11
src/api/group/types.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface GroupInfoDto {
|
||||||
|
name: string
|
||||||
|
displayName: string
|
||||||
|
special: boolean
|
||||||
|
}
|
|
@ -8,13 +8,13 @@ import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||||
import { HistoryEntryDto, HistoryEntryPutDto, HistoryEntryUpdateDto } from './types'
|
import { HistoryEntryDto, HistoryEntryPutDto, HistoryEntryUpdateDto } from './types'
|
||||||
|
|
||||||
export const getHistory = async (): Promise<HistoryEntryDto[]> => {
|
export const getHistory = async (): Promise<HistoryEntryDto[]> => {
|
||||||
const response = await fetch(getApiUrl() + '/history')
|
const response = await fetch(getApiUrl() + '/me/history')
|
||||||
expectResponseCode(response)
|
expectResponseCode(response)
|
||||||
return await response.json() as Promise<HistoryEntryDto[]>
|
return await response.json() as Promise<HistoryEntryDto[]>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const postHistory = async (entries: HistoryEntryPutDto[]): Promise<void> => {
|
export const postHistory = async (entries: HistoryEntryPutDto[]): Promise<void> => {
|
||||||
const response = await fetch(getApiUrl() + '/history', {
|
const response = await fetch(getApiUrl() + '/me/history', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify(entries)
|
body: JSON.stringify(entries)
|
||||||
|
@ -23,7 +23,7 @@ export const postHistory = async (entries: HistoryEntryPutDto[]): Promise<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const updateHistoryEntryPinStatus = async (noteId: string, entry: HistoryEntryUpdateDto): Promise<void> => {
|
export const updateHistoryEntryPinStatus = async (noteId: string, entry: HistoryEntryUpdateDto): Promise<void> => {
|
||||||
const response = await fetch(getApiUrl() + '/history/' + noteId, {
|
const response = await fetch(getApiUrl() + '/me/history/' + noteId, {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'PUT',
|
method: 'PUT',
|
||||||
body: JSON.stringify(entry)
|
body: JSON.stringify(entry)
|
||||||
|
@ -32,7 +32,7 @@ export const updateHistoryEntryPinStatus = async (noteId: string, entry: History
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteHistoryEntry = async (noteId: string): Promise<void> => {
|
export const deleteHistoryEntry = async (noteId: string): Promise<void> => {
|
||||||
const response = await fetch(getApiUrl() + '/history/' + noteId, {
|
const response = await fetch(getApiUrl() + '/me/history/' + noteId, {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
@ -40,7 +40,7 @@ export const deleteHistoryEntry = async (noteId: string): Promise<void> => {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteHistory = async (): Promise<void> => {
|
export const deleteHistory = async (): Promise<void> => {
|
||||||
const response = await fetch(getApiUrl() + '/history', {
|
const response = await fetch(getApiUrl() + '/me/history', {
|
||||||
...defaultFetchConfig,
|
...defaultFetchConfig,
|
||||||
method: 'DELETE'
|
method: 'DELETE'
|
||||||
})
|
})
|
||||||
|
|
|
@ -6,9 +6,10 @@
|
||||||
|
|
||||||
import { UserResponse } from '../users/types'
|
import { UserResponse } from '../users/types'
|
||||||
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||||
|
import { isMockMode } from '../../utils/test-modes'
|
||||||
|
|
||||||
export const getMe = async (): Promise<UserResponse> => {
|
export const getMe = async (): Promise<UserResponse> => {
|
||||||
const response = await fetch(getApiUrl() + '/me', {
|
const response = await fetch(getApiUrl() + `/me${ isMockMode() ? '-get' : '' }`, {
|
||||||
...defaultFetchConfig
|
...defaultFetchConfig
|
||||||
})
|
})
|
||||||
expectResponseCode(response)
|
expectResponseCode(response)
|
||||||
|
|
28
src/api/notes/dto-methods.ts
Normal file
28
src/api/notes/dto-methods.ts
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { NoteDto } from './types'
|
||||||
|
import { NoteDetails } from '../../redux/note-details/types'
|
||||||
|
import { DateTime } from 'luxon'
|
||||||
|
import { initialState } from '../../redux/note-details/reducers'
|
||||||
|
|
||||||
|
export const noteDtoToNoteDetails = (note: NoteDto): NoteDetails => {
|
||||||
|
return {
|
||||||
|
markdownContent: note.content,
|
||||||
|
frontmatter: initialState.frontmatter,
|
||||||
|
id: note.metadata.id,
|
||||||
|
noteTitle: initialState.noteTitle,
|
||||||
|
createTime: DateTime.fromISO(note.metadata.createTime),
|
||||||
|
lastChange: {
|
||||||
|
userName: note.metadata.updateUser.userName,
|
||||||
|
timestamp: DateTime.fromISO(note.metadata.updateTime)
|
||||||
|
},
|
||||||
|
firstHeading: initialState.firstHeading,
|
||||||
|
viewCount: note.metadata.viewCount,
|
||||||
|
alias: note.metadata.alias,
|
||||||
|
authorship: note.metadata.editedBy
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,31 +5,17 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
import { defaultFetchConfig, expectResponseCode, getApiUrl } from '../utils'
|
||||||
|
import { NoteDto } from './types'
|
||||||
|
import { isMockMode } from '../../utils/test-modes'
|
||||||
|
|
||||||
interface LastChange {
|
export const getNote = async (noteId: string): Promise<NoteDto> => {
|
||||||
userId: string
|
|
||||||
timestamp: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Note {
|
|
||||||
id: string
|
|
||||||
alias: string
|
|
||||||
lastChange: LastChange
|
|
||||||
viewCount: number
|
|
||||||
createTime: number
|
|
||||||
content: string
|
|
||||||
authorship: number[]
|
|
||||||
preVersionTwoNote: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getNote = async (noteId: string): Promise<Note> => {
|
|
||||||
// The "-get" suffix is necessary, because in our mock api (filesystem) the note id might already be a folder.
|
// The "-get" suffix is necessary, because in our mock api (filesystem) the note id might already be a folder.
|
||||||
// TODO: [mrdrogdrog] replace -get with actual api route as soon as api backend is ready.
|
// TODO: [mrdrogdrog] replace -get with actual api route as soon as api backend is ready.
|
||||||
const response = await fetch(getApiUrl() + `/notes/${ noteId }-get`, {
|
const response = await fetch(getApiUrl() + `/notes/${ noteId }${ isMockMode() ? '-get' : '' }`, {
|
||||||
...defaultFetchConfig
|
...defaultFetchConfig
|
||||||
})
|
})
|
||||||
expectResponseCode(response)
|
expectResponseCode(response)
|
||||||
return await response.json() as Promise<Note>
|
return await response.json() as Promise<NoteDto>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const deleteNote = async (noteId: string): Promise<void> => {
|
export const deleteNote = async (noteId: string): Promise<void> => {
|
||||||
|
|
53
src/api/notes/types.d.ts
vendored
Normal file
53
src/api/notes/types.d.ts
vendored
Normal file
|
@ -0,0 +1,53 @@
|
||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { UserInfoDto } from '../users/types'
|
||||||
|
import { GroupInfoDto } from '../group/types'
|
||||||
|
|
||||||
|
export interface NoteDto {
|
||||||
|
content: string
|
||||||
|
metadata: NoteMetadataDto
|
||||||
|
editedByAtPosition: NoteAuthorshipDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoteMetadataDto {
|
||||||
|
id: string
|
||||||
|
alias: string
|
||||||
|
version: number
|
||||||
|
title: string
|
||||||
|
description: string
|
||||||
|
tags: string[]
|
||||||
|
updateTime: string
|
||||||
|
updateUser: UserInfoDto
|
||||||
|
viewCount: number
|
||||||
|
createTime: string
|
||||||
|
editedBy: string[]
|
||||||
|
permissions: NotePermissionsDto
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoteAuthorshipDto {
|
||||||
|
userName: string
|
||||||
|
startPos: number
|
||||||
|
endPos: number
|
||||||
|
createdAt: string
|
||||||
|
updatedAt: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NotePermissionsDto {
|
||||||
|
owner: UserInfoDto
|
||||||
|
sharedToUsers: NoteUserPermissionEntryDto[]
|
||||||
|
sharedToGroups: NoteGroupPermissionEntryDto[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoteUserPermissionEntryDto {
|
||||||
|
user: UserInfoDto
|
||||||
|
canEdit: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NoteGroupPermissionEntryDto {
|
||||||
|
group: GroupInfoDto
|
||||||
|
canEdit: boolean
|
||||||
|
}
|
7
src/api/users/types.d.ts
vendored
7
src/api/users/types.d.ts
vendored
|
@ -12,3 +12,10 @@ export interface UserResponse {
|
||||||
photo: string
|
photo: string
|
||||||
provider: LoginProvider
|
provider: LoginProvider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface UserInfoDto {
|
||||||
|
userName: string
|
||||||
|
displayName: string
|
||||||
|
photo: string
|
||||||
|
email: string
|
||||||
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import { Redirect } from 'react-router'
|
||||||
import { useParams } from 'react-router-dom'
|
import { useParams } from 'react-router-dom'
|
||||||
import { getNote } from '../../../api/notes'
|
import { getNote } from '../../../api/notes'
|
||||||
import { NotFoundErrorScreen } from './not-found-error-screen'
|
import { NotFoundErrorScreen } from './not-found-error-screen'
|
||||||
|
import { NoteDto } from '../../../api/notes/types'
|
||||||
|
|
||||||
interface RouteParameters {
|
interface RouteParameters {
|
||||||
id: string
|
id: string
|
||||||
|
@ -20,7 +21,7 @@ export const Redirector: React.FC = () => {
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getNote(id)
|
getNote(id)
|
||||||
.then((noteFromAPI) => setError(!noteFromAPI.preVersionTwoNote))
|
.then((noteFromAPI: NoteDto) => setError(noteFromAPI.metadata.version !== 1))
|
||||||
.catch(() => setError(true))
|
.catch(() => setError(true))
|
||||||
}, [id])
|
}, [id])
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ export const DocumentReadOnlyPage: React.FC = () => {
|
||||||
</div>
|
</div>
|
||||||
<ShowIf condition={ !error && !loading }>
|
<ShowIf condition={ !error && !loading }>
|
||||||
<DocumentInfobar
|
<DocumentInfobar
|
||||||
changedAuthor={ noteDetails.lastChange.userId ?? '' }
|
changedAuthor={ noteDetails.lastChange.userName ?? '' }
|
||||||
changedTime={ noteDetails.lastChange.timestamp }
|
changedTime={ noteDetails.lastChange.timestamp }
|
||||||
createdAuthor={ 'Test' }
|
createdAuthor={ 'Test' }
|
||||||
createdTime={ noteDetails.createTime }
|
createdTime={ noteDetails.createTime }
|
||||||
|
|
|
@ -7,16 +7,13 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Col, Row } from 'react-bootstrap'
|
import { Col, Row } from 'react-bootstrap'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { useSelector } from 'react-redux'
|
|
||||||
import links from '../../../../links.json'
|
import links from '../../../../links.json'
|
||||||
import { ApplicationState } from '../../../../redux'
|
|
||||||
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
import { TranslatedExternalLink } from '../../../common/links/translated-external-link'
|
||||||
import { TranslatedInternalLink } from '../../../common/links/translated-internal-link'
|
import { TranslatedInternalLink } from '../../../common/links/translated-internal-link'
|
||||||
|
|
||||||
export const Links: React.FC = () => {
|
export const Links: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const backendIssueTracker = useSelector((state: ApplicationState) => state.config.version.issueTrackerUrl)
|
|
||||||
return (
|
return (
|
||||||
<Row className={ 'justify-content-center pt-4' }>
|
<Row className={ 'justify-content-center pt-4' }>
|
||||||
<Col lg={ 4 }>
|
<Col lg={ 4 }>
|
||||||
|
@ -43,7 +40,7 @@ export const Links: React.FC = () => {
|
||||||
<li>
|
<li>
|
||||||
<TranslatedExternalLink
|
<TranslatedExternalLink
|
||||||
i18nKey='editor.help.contacts.reportIssue'
|
i18nKey='editor.help.contacts.reportIssue'
|
||||||
href={ backendIssueTracker }
|
href={ links.backendIssues }
|
||||||
icon='tag'
|
icon='tag'
|
||||||
className='text-primary'
|
className='text-primary'
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -8,7 +8,7 @@ import React, { Fragment, useCallback, useEffect, useRef, useState } from 'react
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-activated'
|
import { useIsDarkModeActivated } from '../../../hooks/common/use-is-dark-mode-activated'
|
||||||
import { ApplicationState } from '../../../redux'
|
import { ApplicationState } from '../../../redux'
|
||||||
import { isTestMode } from '../../../utils/is-test-mode'
|
import { isTestMode } from '../../../utils/test-modes'
|
||||||
import { RendererProps } from '../../render-page/markdown-document'
|
import { RendererProps } from '../../render-page/markdown-document'
|
||||||
import { ImageDetails, RendererType } from '../../render-page/rendering-message'
|
import { ImageDetails, RendererType } from '../../render-page/rendering-message'
|
||||||
import { useContextOrStandaloneIframeCommunicator } from '../render-context/iframe-communicator-context-provider'
|
import { useContextOrStandaloneIframeCommunicator } from '../render-context/iframe-communicator-context-provider'
|
||||||
|
@ -44,7 +44,7 @@ export const RenderIframe: React.FC<RenderIframeProps> = (
|
||||||
|
|
||||||
const frameReference = useRef<HTMLIFrameElement>(null)
|
const frameReference = useRef<HTMLIFrameElement>(null)
|
||||||
const rendererOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.rendererOrigin)
|
const rendererOrigin = useSelector((state: ApplicationState) => state.config.iframeCommunication.rendererOrigin)
|
||||||
const renderPageUrl = `${ rendererOrigin }/render`
|
const renderPageUrl = `${ rendererOrigin }render`
|
||||||
const resetRendererReady = useCallback(() => setRendererReady(false), [])
|
const resetRendererReady = useCallback(() => setRendererReady(false), [])
|
||||||
const iframeCommunicator = useContextOrStandaloneIframeCommunicator()
|
const iframeCommunicator = useContextOrStandaloneIframeCommunicator()
|
||||||
const onIframeLoad = useOnIframeLoad(frameReference, iframeCommunicator, rendererOrigin, renderPageUrl, resetRendererReady)
|
const onIframeLoad = useOnIframeLoad(frameReference, iframeCommunicator, rendererOrigin, renderPageUrl, resetRendererReady)
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*
|
/*
|
||||||
SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||||
|
*
|
||||||
SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React, { Fragment, useCallback, useState } from 'react'
|
import React, { Fragment, useCallback, useState } from 'react'
|
||||||
|
@ -46,23 +46,19 @@ export const HistoryContent: React.FC<HistoryContentProps> = ({ viewState, entri
|
||||||
const [lastPageIndex, setLastPageIndex] = useState(0)
|
const [lastPageIndex, setLastPageIndex] = useState(0)
|
||||||
|
|
||||||
const onPinClick = useCallback((noteId: string) => {
|
const onPinClick = useCallback((noteId: string) => {
|
||||||
toggleHistoryEntryPinning(noteId).catch(
|
toggleHistoryEntryPinning(noteId)
|
||||||
showErrorNotification(t('landing.history.error.updateEntry.text'))
|
.catch(showErrorNotification(t('landing.history.error.updateEntry.text')))
|
||||||
)
|
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
const onDeleteClick = useCallback((noteId: string) => {
|
const onDeleteClick = useCallback((noteId: string) => {
|
||||||
deleteNote(noteId).then(() => {
|
deleteNote(noteId)
|
||||||
return removeHistoryEntry(noteId)
|
.then(() => removeHistoryEntry(noteId))
|
||||||
}).catch(
|
.catch(showErrorNotification(t('landing.history.error.deleteNote.text')))
|
||||||
showErrorNotification(t('landing.history.error.deleteNote.text'))
|
|
||||||
)
|
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
const onRemoveClick = useCallback((noteId: string) => {
|
const onRemoveClick = useCallback((noteId: string) => {
|
||||||
removeHistoryEntry(noteId).catch(
|
removeHistoryEntry(noteId)
|
||||||
showErrorNotification(t('landing.history.error.deleteEntry.text'))
|
.catch(showErrorNotification(t('landing.history.error.deleteEntry.text')))
|
||||||
)
|
|
||||||
}, [t])
|
}, [t])
|
||||||
|
|
||||||
if (entries.length === 0) {
|
if (entries.length === 0) {
|
||||||
|
|
|
@ -18,7 +18,7 @@ import equal from 'fast-deep-equal'
|
||||||
export const PoweredByLinks: React.FC = () => {
|
export const PoweredByLinks: React.FC = () => {
|
||||||
useTranslation()
|
useTranslation()
|
||||||
|
|
||||||
const specialLinks = useSelector((state: ApplicationState) => Object.entries(state.config.specialLinks) as [string, string][], equal)
|
const specialUrls: [string, string][] = useSelector((state: ApplicationState) => Object.entries(state.config.specialUrls) as [string, string][], equal)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p>
|
<p>
|
||||||
|
@ -28,7 +28,7 @@ export const PoweredByLinks: React.FC = () => {
|
||||||
|
|
|
|
||||||
<TranslatedInternalLink href='/n/release-notes' i18nKey='landing.footer.releases'/>
|
<TranslatedInternalLink href='/n/release-notes' i18nKey='landing.footer.releases'/>
|
||||||
{
|
{
|
||||||
specialLinks.map(([i18nKey, href]) =>
|
specialUrls.map(([i18nKey, href]) =>
|
||||||
<Fragment key={ i18nKey }>
|
<Fragment key={ i18nKey }>
|
||||||
|
|
|
|
||||||
<TranslatedExternalLink href={ href } i18nKey={ 'landing.footer.' + i18nKey }/>
|
<TranslatedExternalLink href={ href } i18nKey={ 'landing.footer.' + i18nKey }/>
|
||||||
|
|
|
@ -4,17 +4,32 @@
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import React from 'react'
|
import React, { useMemo } from 'react'
|
||||||
import { CommonModal, CommonModalProps } from '../../../common/modals/common-modal'
|
import { CommonModal, CommonModalProps } from '../../../common/modals/common-modal'
|
||||||
import { Modal, Row } from 'react-bootstrap'
|
import { Modal, Row } from 'react-bootstrap'
|
||||||
import { VersionInfoModalColumn } from './version-info-modal-column'
|
import { VersionInfoModalColumn } from './version-info-modal-column'
|
||||||
import frontendVersion from '../../../../version.json'
|
import frontendVersion from '../../../../version.json'
|
||||||
|
import links from '../../../../links.json'
|
||||||
import { useSelector } from 'react-redux'
|
import { useSelector } from 'react-redux'
|
||||||
import { ApplicationState } from '../../../../redux'
|
import { ApplicationState } from '../../../../redux'
|
||||||
import equal from 'fast-deep-equal'
|
import equal from 'fast-deep-equal'
|
||||||
|
import { BackendVersion } from '../../../../api/config/types'
|
||||||
|
|
||||||
export const VersionInfoModal: React.FC<CommonModalProps> = ({ onHide, show }) => {
|
export const VersionInfoModal: React.FC<CommonModalProps> = ({ onHide, show }) => {
|
||||||
const serverVersion = useSelector((state: ApplicationState) => state.config.version, equal)
|
const serverVersion: BackendVersion = useSelector((state: ApplicationState) => state.config.version, equal)
|
||||||
|
const backendVersion = useMemo(() => {
|
||||||
|
const version = `${serverVersion.major}.${serverVersion.minor}.${serverVersion.patch}`
|
||||||
|
|
||||||
|
if (serverVersion.preRelease) {
|
||||||
|
return `${version}-${serverVersion.preRelease}`
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverVersion.commit) {
|
||||||
|
return serverVersion.commit
|
||||||
|
}
|
||||||
|
return version
|
||||||
|
}, [serverVersion])
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CommonModal data-cy={ 'version-modal' } show={ show } onHide={ onHide } closeButton={ true }
|
<CommonModal data-cy={ 'version-modal' } show={ show } onHide={ onHide } closeButton={ true }
|
||||||
|
@ -23,9 +38,9 @@ export const VersionInfoModal: React.FC<CommonModalProps> = ({ onHide, show }) =
|
||||||
<Row>
|
<Row>
|
||||||
<VersionInfoModalColumn
|
<VersionInfoModalColumn
|
||||||
titleI18nKey={ 'landing.versionInfo.serverVersion' }
|
titleI18nKey={ 'landing.versionInfo.serverVersion' }
|
||||||
version={ serverVersion.version }
|
version={ backendVersion }
|
||||||
issueTrackerLink={ serverVersion.issueTrackerUrl }
|
issueTrackerLink={ links.backendIssues }
|
||||||
sourceCodeLink={ serverVersion.sourceCodeUrl }/>
|
sourceCodeLink={ links.backendSourceCode }/>
|
||||||
<VersionInfoModalColumn
|
<VersionInfoModalColumn
|
||||||
titleI18nKey={ 'landing.versionInfo.clientVersion' }
|
titleI18nKey={ 'landing.versionInfo.clientVersion' }
|
||||||
version={ frontendVersion.version }
|
version={ frontendVersion.version }
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { ApplicationState } from '../../redux'
|
||||||
import { TranslatedExternalLink } from '../common/links/translated-external-link'
|
import { TranslatedExternalLink } from '../common/links/translated-external-link'
|
||||||
import { ShowIf } from '../common/show-if/show-if'
|
import { ShowIf } from '../common/show-if/show-if'
|
||||||
import { getAndSetUser } from '../login-page/auth/utils'
|
import { getAndSetUser } from '../login-page/auth/utils'
|
||||||
|
import { SpecialUrls } from '../../api/config/types'
|
||||||
|
|
||||||
export enum RegisterError {
|
export enum RegisterError {
|
||||||
NONE = 'none',
|
NONE = 'none',
|
||||||
|
@ -24,7 +25,7 @@ export enum RegisterError {
|
||||||
export const RegisterPage: React.FC = () => {
|
export const RegisterPage: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister)
|
const allowRegister = useSelector((state: ApplicationState) => state.config.allowRegister)
|
||||||
const specialLinks = useSelector((state: ApplicationState) => state.config.specialLinks)
|
const specialUrls: SpecialUrls = useSelector((state: ApplicationState) => state.config.specialUrls)
|
||||||
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
const userExists = useSelector((state: ApplicationState) => !!state.user)
|
||||||
|
|
||||||
const [username, setUsername] = useState('')
|
const [username, setUsername] = useState('')
|
||||||
|
@ -112,17 +113,17 @@ export const RegisterPage: React.FC = () => {
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<ShowIf condition={ !!specialLinks?.termsOfUse || !!specialLinks?.privacy }>
|
<ShowIf condition={ !!specialUrls?.termsOfUse || !!specialUrls?.privacy }>
|
||||||
<Trans i18nKey='login.register.infoTermsPrivacy'/>
|
<Trans i18nKey='login.register.infoTermsPrivacy'/>
|
||||||
<ul>
|
<ul>
|
||||||
<ShowIf condition={ !!specialLinks?.termsOfUse }>
|
<ShowIf condition={ !!specialUrls?.termsOfUse }>
|
||||||
<li>
|
<li>
|
||||||
<TranslatedExternalLink i18nKey='landing.footer.termsOfUse' href={ specialLinks.termsOfUse }/>
|
<TranslatedExternalLink i18nKey='landing.footer.termsOfUse' href={ specialUrls.termsOfUse }/>
|
||||||
</li>
|
</li>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
<ShowIf condition={ !!specialLinks?.privacy }>
|
<ShowIf condition={ !!specialUrls?.privacy }>
|
||||||
<li>
|
<li>
|
||||||
<TranslatedExternalLink i18nKey='landing.footer.privacy' href={ specialLinks.privacy }/>
|
<TranslatedExternalLink i18nKey='landing.footer.privacy' href={ specialUrls.privacy }/>
|
||||||
</li>
|
</li>
|
||||||
</ShowIf>
|
</ShowIf>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
|
@ -22,7 +22,7 @@ import { store } from './redux'
|
||||||
import * as serviceWorkerRegistration from './service-worker-registration'
|
import * as serviceWorkerRegistration from './service-worker-registration'
|
||||||
import './style/dark.scss'
|
import './style/dark.scss'
|
||||||
import './style/index.scss'
|
import './style/index.scss'
|
||||||
import { isTestMode } from './utils/is-test-mode'
|
import { isTestMode } from './utils/test-modes'
|
||||||
|
|
||||||
const EditorPage = React.lazy(() => import(/* webpackPrefetch: true *//* webpackChunkName: "editor" */ './components/editor-page/editor-page'))
|
const EditorPage = React.lazy(() => import(/* webpackPrefetch: true *//* webpackChunkName: "editor" */ './components/editor-page/editor-page'))
|
||||||
const RenderPage = React.lazy(() => import (/* webpackPrefetch: true *//* webpackChunkName: "renderPage" */ './components/render-page/render-page'))
|
const RenderPage = React.lazy(() => import (/* webpackPrefetch: true *//* webpackChunkName: "renderPage" */ './components/render-page/render-page'))
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
"community": "https://community.hedgedoc.org",
|
"community": "https://community.hedgedoc.org",
|
||||||
"faq": "https://hedgedoc.org/faq/",
|
"faq": "https://hedgedoc.org/faq/",
|
||||||
"githubOrg": "https://github.com/hedgedoc/",
|
"githubOrg": "https://github.com/hedgedoc/",
|
||||||
|
"backendSourceCode": "https://github.com/hedgedoc/hedgedoc",
|
||||||
|
"backendIssues": "https://github.com/hedgedoc/hedgedoc/issues",
|
||||||
"mastodon": "https://social.hedgedoc.org",
|
"mastodon": "https://social.hedgedoc.org",
|
||||||
"translate": "https://translate.hedgedoc.org",
|
"translate": "https://translate.hedgedoc.org",
|
||||||
"webpage": "https://hedgedoc.org"
|
"webpage": "https://hedgedoc.org"
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { Reducer } from 'redux'
|
||||||
import { BannerActions, BannerActionType, BannerState, SetBannerAction } from './types'
|
import { BannerActions, BannerActionType, BannerState, SetBannerAction } from './types'
|
||||||
|
|
||||||
export const initialState: BannerState = {
|
export const initialState: BannerState = {
|
||||||
show: true,
|
show: false,
|
||||||
text: '',
|
text: '',
|
||||||
timestamp: ''
|
timestamp: ''
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,15 +40,15 @@ export const initialState: Config = {
|
||||||
maxDocumentLength: 0,
|
maxDocumentLength: 0,
|
||||||
useImageProxy: false,
|
useImageProxy: false,
|
||||||
plantumlServer: null,
|
plantumlServer: null,
|
||||||
specialLinks: {
|
specialUrls: {
|
||||||
privacy: '',
|
privacy: '',
|
||||||
termsOfUse: '',
|
termsOfUse: '',
|
||||||
imprint: ''
|
imprint: ''
|
||||||
},
|
},
|
||||||
version: {
|
version: {
|
||||||
version: '',
|
major: -1,
|
||||||
sourceCodeUrl: '',
|
minor: -1,
|
||||||
issueTrackerUrl: ''
|
patch: -1,
|
||||||
},
|
},
|
||||||
iframeCommunication: {
|
iframeCommunication: {
|
||||||
editorOrigin: '',
|
editorOrigin: '',
|
||||||
|
|
|
@ -91,8 +91,8 @@ export const toggleHistoryEntryPinning = async (noteId: string): Promise<void> =
|
||||||
updateLocalHistoryEntry(noteId, entryToUpdate)
|
updateLocalHistoryEntry(noteId, entryToUpdate)
|
||||||
} else {
|
} else {
|
||||||
const historyUpdateDto = historyEntryToHistoryEntryUpdateDto(entryToUpdate)
|
const historyUpdateDto = historyEntryToHistoryEntryUpdateDto(entryToUpdate)
|
||||||
updateHistoryEntryRedux(noteId, entryToUpdate)
|
|
||||||
await updateHistoryEntryPinStatus(noteId, historyUpdateDto)
|
await updateHistoryEntryPinStatus(noteId, historyUpdateDto)
|
||||||
|
updateHistoryEntryRedux(noteId, entryToUpdate)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { store } from '..'
|
import { store } from '..'
|
||||||
import { Note } from '../../api/notes'
|
import { NoteDto } from '../../api/notes/types'
|
||||||
import { NoteFrontmatter } from '../../components/editor-page/note-frontmatter/note-frontmatter'
|
import { NoteFrontmatter } from '../../components/editor-page/note-frontmatter/note-frontmatter'
|
||||||
import { initialState } from './reducers'
|
import { initialState } from './reducers'
|
||||||
import {
|
import {
|
||||||
|
@ -24,7 +24,7 @@ export const setNoteMarkdownContent = (content: string): void => {
|
||||||
} as SetNoteDetailsAction)
|
} as SetNoteDetailsAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const setNoteDataFromServer = (apiResponse: Note): void => {
|
export const setNoteDataFromServer = (apiResponse: NoteDto): void => {
|
||||||
store.dispatch({
|
store.dispatch({
|
||||||
type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER,
|
type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER,
|
||||||
note: apiResponse
|
note: apiResponse
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
|
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
import { Reducer } from 'redux'
|
import { Reducer } from 'redux'
|
||||||
import { Note } from '../../api/notes'
|
|
||||||
import {
|
import {
|
||||||
NoteFrontmatter,
|
NoteFrontmatter,
|
||||||
NoteTextDirection,
|
NoteTextDirection,
|
||||||
|
@ -22,6 +21,7 @@ import {
|
||||||
SetNoteFrontmatterFromRenderingAction,
|
SetNoteFrontmatterFromRenderingAction,
|
||||||
UpdateNoteTitleByFirstHeadingAction
|
UpdateNoteTitleByFirstHeadingAction
|
||||||
} from './types'
|
} from './types'
|
||||||
|
import { noteDtoToNoteDetails } from '../../api/notes/dto-methods'
|
||||||
|
|
||||||
export const initialState: NoteDetails = {
|
export const initialState: NoteDetails = {
|
||||||
markdownContent: '',
|
markdownContent: '',
|
||||||
|
@ -29,10 +29,9 @@ export const initialState: NoteDetails = {
|
||||||
createTime: DateTime.fromSeconds(0),
|
createTime: DateTime.fromSeconds(0),
|
||||||
lastChange: {
|
lastChange: {
|
||||||
timestamp: DateTime.fromSeconds(0),
|
timestamp: DateTime.fromSeconds(0),
|
||||||
userId: ''
|
userName: ''
|
||||||
},
|
},
|
||||||
alias: '',
|
alias: '',
|
||||||
preVersionTwoNote: false,
|
|
||||||
viewCount: 0,
|
viewCount: 0,
|
||||||
authorship: [],
|
authorship: [],
|
||||||
noteTitle: '',
|
noteTitle: '',
|
||||||
|
@ -67,7 +66,7 @@ export const NoteDetailsReducer: Reducer<NoteDetails, NoteDetailsAction> = (stat
|
||||||
noteTitle: generateNoteTitle(state.frontmatter, (action as UpdateNoteTitleByFirstHeadingAction).firstHeading)
|
noteTitle: generateNoteTitle(state.frontmatter, (action as UpdateNoteTitleByFirstHeadingAction).firstHeading)
|
||||||
}
|
}
|
||||||
case NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER:
|
case NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER:
|
||||||
return convertNoteToNoteDetails((action as SetNoteDetailsFromServerAction).note)
|
return noteDtoToNoteDetails((action as SetNoteDetailsFromServerAction).note)
|
||||||
case NoteDetailsActionType.SET_NOTE_FRONTMATTER:
|
case NoteDetailsActionType.SET_NOTE_FRONTMATTER:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -111,21 +110,4 @@ const generateNoteTitle = (frontmatter: NoteFrontmatter, firstHeading?: string)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const convertNoteToNoteDetails = (note: Note): NoteDetails => {
|
|
||||||
return {
|
|
||||||
markdownContent: note.content,
|
|
||||||
frontmatter: initialState.frontmatter,
|
|
||||||
id: note.id,
|
|
||||||
noteTitle: initialState.noteTitle,
|
|
||||||
createTime: DateTime.fromSeconds(note.createTime),
|
|
||||||
lastChange: {
|
|
||||||
userId: note.lastChange.userId,
|
|
||||||
timestamp: DateTime.fromSeconds(note.lastChange.timestamp)
|
|
||||||
},
|
|
||||||
firstHeading: initialState.firstHeading,
|
|
||||||
preVersionTwoNote: note.preVersionTwoNote,
|
|
||||||
viewCount: note.viewCount,
|
|
||||||
alias: note.alias,
|
|
||||||
authorship: note.authorship
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -6,8 +6,8 @@
|
||||||
|
|
||||||
import { DateTime } from 'luxon'
|
import { DateTime } from 'luxon'
|
||||||
import { Action } from 'redux'
|
import { Action } from 'redux'
|
||||||
import { Note } from '../../api/notes'
|
|
||||||
import { NoteFrontmatter } from '../../components/editor-page/note-frontmatter/note-frontmatter'
|
import { NoteFrontmatter } from '../../components/editor-page/note-frontmatter/note-frontmatter'
|
||||||
|
import { NoteDto } from '../../api/notes/types'
|
||||||
|
|
||||||
export enum NoteDetailsActionType {
|
export enum NoteDetailsActionType {
|
||||||
SET_DOCUMENT_CONTENT = 'note-details/set',
|
SET_DOCUMENT_CONTENT = 'note-details/set',
|
||||||
|
@ -18,7 +18,7 @@ export enum NoteDetailsActionType {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LastChange {
|
interface LastChange {
|
||||||
userId: string
|
userName: string
|
||||||
timestamp: DateTime
|
timestamp: DateTime
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,10 +27,9 @@ export interface NoteDetails {
|
||||||
id: string
|
id: string
|
||||||
createTime: DateTime
|
createTime: DateTime
|
||||||
lastChange: LastChange
|
lastChange: LastChange
|
||||||
preVersionTwoNote: boolean
|
|
||||||
viewCount: number
|
viewCount: number
|
||||||
alias: string
|
alias: string
|
||||||
authorship: number[]
|
authorship: string[]
|
||||||
noteTitle: string
|
noteTitle: string
|
||||||
firstHeading?: string
|
firstHeading?: string
|
||||||
frontmatter: NoteFrontmatter
|
frontmatter: NoteFrontmatter
|
||||||
|
@ -47,7 +46,7 @@ export interface SetNoteDetailsAction extends NoteDetailsAction {
|
||||||
|
|
||||||
export interface SetNoteDetailsFromServerAction extends NoteDetailsAction {
|
export interface SetNoteDetailsFromServerAction extends NoteDetailsAction {
|
||||||
type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER
|
type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER
|
||||||
note: Note
|
note: NoteDto
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface UpdateNoteTitleByFirstHeadingAction extends NoteDetailsAction {
|
export interface UpdateNoteTitleByFirstHeadingAction extends NoteDetailsAction {
|
||||||
|
|
|
@ -39,9 +39,7 @@ export const dismissUiNotification = (notificationId: number): void => {
|
||||||
} as DismissUiNotificationAction)
|
} as DismissUiNotificationAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Promises catch errors as any.
|
export const showErrorNotification = (message: string) => (error: Error): void => {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any,@typescript-eslint/explicit-module-boundary-types
|
|
||||||
export const showErrorNotification = (message: string) => (error: any): void => {
|
|
||||||
console.error(message, error)
|
console.error(message, error)
|
||||||
dispatchUiNotification(i18n.t('common.errorOccurred'), message, DEFAULT_DURATION_IN_SECONDS, 'exclamation-triangle')
|
dispatchUiNotification(i18n.t('common.errorOccurred'), message, DEFAULT_DURATION_IN_SECONDS, 'exclamation-triangle')
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,3 +7,7 @@
|
||||||
export const isTestMode = (): boolean => {
|
export const isTestMode = (): boolean => {
|
||||||
return !!process.env.REACT_APP_TEST_MODE
|
return !!process.env.REACT_APP_TEST_MODE
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const isMockMode = (): boolean => {
|
||||||
|
return process.env.REACT_APP_BACKEND === undefined
|
||||||
|
}
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"version": "0.0",
|
"version": "0.0",
|
||||||
"sourceCodeUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
|
"sourceCodeUrl": "https://github.com/hedgedoc/react-client",
|
||||||
"issueTrackerUrl": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
|
"issueTrackerUrl": "https://github.com/hedgedoc/react-client/issues"
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue