mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-18 09:04:44 -04:00
Adapt react-client to use the real backend API (#1545)
Co-authored-by: Philip Molares <philip.molares@udo.edu> Co-authored-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
3399ed2023
commit
26f90505ff
227 changed files with 4726 additions and 2310 deletions
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { store } from '..'
|
||||
import type { ApiUrlObject, SetApiUrlAction } from './types'
|
||||
import { ApiUrlActionType } from './types'
|
||||
|
||||
export const setApiUrl = (state: ApiUrlObject): void => {
|
||||
store.dispatch({
|
||||
type: ApiUrlActionType.SET_API_URL,
|
||||
state
|
||||
} as SetApiUrlAction)
|
||||
}
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Reducer } from 'redux'
|
||||
import type { ApiUrlActions, ApiUrlObject } from './types'
|
||||
import { ApiUrlActionType } from './types'
|
||||
|
||||
export const initialState: ApiUrlObject = {
|
||||
apiUrl: ''
|
||||
}
|
||||
|
||||
export const ApiUrlReducer: Reducer<ApiUrlObject, ApiUrlActions> = (
|
||||
state: ApiUrlObject = initialState,
|
||||
action: ApiUrlActions
|
||||
) => {
|
||||
switch (action.type) {
|
||||
case ApiUrlActionType.SET_API_URL:
|
||||
return action.state
|
||||
default:
|
||||
return state
|
||||
}
|
||||
}
|
|
@ -1,22 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Action } from 'redux'
|
||||
|
||||
export enum ApiUrlActionType {
|
||||
SET_API_URL = 'api-url/set'
|
||||
}
|
||||
|
||||
export type ApiUrlActions = SetApiUrlAction
|
||||
|
||||
export interface SetApiUrlAction extends Action<ApiUrlActionType> {
|
||||
type: ApiUrlActionType.SET_API_URL
|
||||
state: ApiUrlObject
|
||||
}
|
||||
|
||||
export interface ApiUrlObject {
|
||||
apiUrl: string
|
||||
}
|
6
src/redux/application-state.d.ts
vendored
6
src/redux/application-state.d.ts
vendored
|
@ -7,20 +7,18 @@
|
|||
import type { OptionalUserState } from './user/types'
|
||||
import type { Config } from '../api/config/types'
|
||||
import type { OptionalMotdState } from './motd/types'
|
||||
import type { HistoryEntry } from './history/types'
|
||||
import type { ApiUrlObject } from './api-url/types'
|
||||
import type { EditorConfig } from './editor/types'
|
||||
import type { DarkModeConfig } from './dark-mode/types'
|
||||
import type { NoteDetails } from './note-details/types/note-details'
|
||||
import type { UiNotificationState } from './ui-notifications/types'
|
||||
import type { RendererStatus } from './renderer-status/types'
|
||||
import type { HistoryEntryWithOrigin } from '../api/history/types'
|
||||
|
||||
export interface ApplicationState {
|
||||
user: OptionalUserState
|
||||
config: Config
|
||||
motd: OptionalMotdState
|
||||
history: HistoryEntry[]
|
||||
apiUrl: ApiUrlObject
|
||||
history: HistoryEntryWithOrigin[]
|
||||
editorConfig: EditorConfig
|
||||
darkMode: DarkModeConfig
|
||||
noteDetails: NoteDetails
|
||||
|
|
|
@ -12,40 +12,24 @@ import { ConfigActionType } from './types'
|
|||
export const initialState: Config = {
|
||||
allowAnonymous: true,
|
||||
allowRegister: true,
|
||||
authProviders: {
|
||||
facebook: false,
|
||||
github: false,
|
||||
twitter: false,
|
||||
gitlab: false,
|
||||
dropbox: false,
|
||||
ldap: false,
|
||||
google: false,
|
||||
saml: false,
|
||||
oauth2: false,
|
||||
local: false
|
||||
},
|
||||
authProviders: [],
|
||||
branding: {
|
||||
name: '',
|
||||
logo: ''
|
||||
},
|
||||
customAuthNames: {
|
||||
ldap: '',
|
||||
oauth2: '',
|
||||
saml: ''
|
||||
},
|
||||
maxDocumentLength: 0,
|
||||
useImageProxy: false,
|
||||
plantumlServer: null,
|
||||
specialUrls: {
|
||||
privacy: '',
|
||||
termsOfUse: '',
|
||||
imprint: ''
|
||||
privacy: undefined,
|
||||
termsOfUse: undefined,
|
||||
imprint: undefined
|
||||
},
|
||||
version: {
|
||||
major: -1,
|
||||
minor: -1,
|
||||
patch: -1
|
||||
major: 0,
|
||||
minor: 0,
|
||||
patch: 0
|
||||
},
|
||||
plantumlServer: undefined,
|
||||
maxDocumentLength: 0,
|
||||
iframeCommunication: {
|
||||
editorOrigin: '',
|
||||
rendererOrigin: ''
|
||||
|
|
|
@ -1,39 +1,34 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { getGlobalState, store } from '../index'
|
||||
import type {
|
||||
HistoryEntry,
|
||||
HistoryExportJson,
|
||||
RemoveEntryAction,
|
||||
SetEntriesAction,
|
||||
UpdateEntryAction,
|
||||
V1HistoryEntry
|
||||
} from './types'
|
||||
import { HistoryActionType, HistoryEntryOrigin } from './types'
|
||||
import type { HistoryExportJson, RemoveEntryAction, SetEntriesAction, UpdateEntryAction, V1HistoryEntry } from './types'
|
||||
import { HistoryActionType } from './types'
|
||||
import { download } from '../../components/common/download/download'
|
||||
import { DateTime } from 'luxon'
|
||||
import {
|
||||
deleteHistory,
|
||||
deleteHistoryEntry,
|
||||
getHistory,
|
||||
postHistory,
|
||||
updateHistoryEntryPinStatus
|
||||
deleteRemoteHistory,
|
||||
deleteRemoteHistoryEntry,
|
||||
getRemoteHistory,
|
||||
setRemoteHistoryEntries,
|
||||
updateRemoteHistoryEntryPinStatus
|
||||
} from '../../api/history'
|
||||
import {
|
||||
historyEntryDtoToHistoryEntry,
|
||||
historyEntryToHistoryEntryPutDto,
|
||||
historyEntryToHistoryEntryUpdateDto
|
||||
} from '../../api/history/dto-methods'
|
||||
import { addRemoteOriginToHistoryEntry, historyEntryToHistoryEntryPutDto } from '../../api/history/dto-methods'
|
||||
import { Logger } from '../../utils/logger'
|
||||
import type { HistoryEntry, HistoryEntryWithOrigin } from '../../api/history/types'
|
||||
import { HistoryEntryOrigin } from '../../api/history/types'
|
||||
import { showErrorNotification } from '../ui-notifications/methods'
|
||||
|
||||
const log = new Logger('Redux > History')
|
||||
|
||||
export const setHistoryEntries = (entries: HistoryEntry[]): void => {
|
||||
/**
|
||||
* Sets the given history entries into the current redux state and updates the local-storage.
|
||||
* @param entries The history entries to set into the redux state.
|
||||
*/
|
||||
export const setHistoryEntries = (entries: HistoryEntryWithOrigin[]): void => {
|
||||
store.dispatch({
|
||||
type: HistoryActionType.SET_ENTRIES,
|
||||
entries
|
||||
|
@ -41,20 +36,32 @@ export const setHistoryEntries = (entries: HistoryEntry[]): void => {
|
|||
storeLocalHistory()
|
||||
}
|
||||
|
||||
export const importHistoryEntries = (entries: HistoryEntry[]): Promise<void> => {
|
||||
/**
|
||||
* Imports the given history entries into redux state and local-storage and remote based on their associated origin label.
|
||||
* @param entries The history entries to import.
|
||||
*/
|
||||
export const importHistoryEntries = (entries: HistoryEntryWithOrigin[]): Promise<unknown> => {
|
||||
setHistoryEntries(entries)
|
||||
return storeRemoteHistory()
|
||||
}
|
||||
|
||||
export const deleteAllHistoryEntries = (): Promise<void> => {
|
||||
/**
|
||||
* Deletes all history entries in the redux, local-storage and on the server.
|
||||
*/
|
||||
export const deleteAllHistoryEntries = (): Promise<unknown> => {
|
||||
store.dispatch({
|
||||
type: HistoryActionType.SET_ENTRIES,
|
||||
entries: []
|
||||
} as SetEntriesAction)
|
||||
storeLocalHistory()
|
||||
return deleteHistory()
|
||||
return deleteRemoteHistory()
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single history entry in the redux.
|
||||
* @param noteId The note id of the history entry to update.
|
||||
* @param newEntry The modified history entry.
|
||||
*/
|
||||
export const updateHistoryEntryRedux = (noteId: string, newEntry: HistoryEntry): void => {
|
||||
store.dispatch({
|
||||
type: HistoryActionType.UPDATE_ENTRY,
|
||||
|
@ -63,15 +70,24 @@ export const updateHistoryEntryRedux = (noteId: string, newEntry: HistoryEntry):
|
|||
} as UpdateEntryAction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a single history entry in the local-storage.
|
||||
* @param noteId The note id of the history entry to update.
|
||||
* @param newEntry The modified history entry.
|
||||
*/
|
||||
export const updateLocalHistoryEntry = (noteId: string, newEntry: HistoryEntry): void => {
|
||||
updateHistoryEntryRedux(noteId, newEntry)
|
||||
storeLocalHistory()
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes a single history entry for a given note id.
|
||||
* @param noteId The note id of the history entry to delete.
|
||||
*/
|
||||
export const removeHistoryEntry = async (noteId: string): Promise<void> => {
|
||||
const entryToDelete = getGlobalState().history.find((entry) => entry.identifier === noteId)
|
||||
if (entryToDelete && entryToDelete.origin === HistoryEntryOrigin.REMOTE) {
|
||||
await deleteHistoryEntry(noteId)
|
||||
await deleteRemoteHistoryEntry(noteId)
|
||||
}
|
||||
store.dispatch({
|
||||
type: HistoryActionType.REMOVE_ENTRY,
|
||||
|
@ -80,6 +96,10 @@ export const removeHistoryEntry = async (noteId: string): Promise<void> => {
|
|||
storeLocalHistory()
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggles the pinning state of a single history entry.
|
||||
* @param noteId The note id of the history entry to update.
|
||||
*/
|
||||
export const toggleHistoryEntryPinning = async (noteId: string): Promise<void> => {
|
||||
const state = getGlobalState().history
|
||||
const entryToUpdate = state.find((entry) => entry.identifier === noteId)
|
||||
|
@ -93,15 +113,17 @@ export const toggleHistoryEntryPinning = async (noteId: string): Promise<void> =
|
|||
if (entryToUpdate.origin === HistoryEntryOrigin.LOCAL) {
|
||||
updateLocalHistoryEntry(noteId, entryToUpdate)
|
||||
} else {
|
||||
const historyUpdateDto = historyEntryToHistoryEntryUpdateDto(entryToUpdate)
|
||||
await updateHistoryEntryPinStatus(noteId, historyUpdateDto)
|
||||
await updateRemoteHistoryEntryPinStatus(noteId, entryToUpdate.pinStatus)
|
||||
updateHistoryEntryRedux(noteId, entryToUpdate)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Exports the current history redux state into a JSON file that will be downloaded by the client.
|
||||
*/
|
||||
export const downloadHistory = (): void => {
|
||||
const history = getGlobalState().history
|
||||
history.forEach((entry: Partial<HistoryEntry>) => {
|
||||
history.forEach((entry: Partial<HistoryEntryWithOrigin>) => {
|
||||
delete entry.origin
|
||||
})
|
||||
const json = JSON.stringify({
|
||||
|
@ -111,22 +133,39 @@ export const downloadHistory = (): void => {
|
|||
download(json, `history_${Date.now()}.json`, 'application/json')
|
||||
}
|
||||
|
||||
export const mergeHistoryEntries = (a: HistoryEntry[], b: HistoryEntry[]): HistoryEntry[] => {
|
||||
/**
|
||||
* Merges two arrays of history entries while removing duplicates.
|
||||
* @param a The first input array of history entries.
|
||||
* @param b The second input array of history entries. This array takes precedence when duplicates were found.
|
||||
* @return The merged array of history entries without duplicates.
|
||||
*/
|
||||
export const mergeHistoryEntries = (
|
||||
a: HistoryEntryWithOrigin[],
|
||||
b: HistoryEntryWithOrigin[]
|
||||
): HistoryEntryWithOrigin[] => {
|
||||
const noDuplicates = a.filter((entryA) => !b.some((entryB) => entryA.identifier === entryB.identifier))
|
||||
return noDuplicates.concat(b)
|
||||
}
|
||||
|
||||
export const convertV1History = (oldHistory: V1HistoryEntry[]): HistoryEntry[] => {
|
||||
/**
|
||||
* Converts an array of local HedgeDoc v1 history entries to HedgeDoc v2 history entries.
|
||||
* @param oldHistory An array of HedgeDoc v1 history entries.
|
||||
* @return An array of HedgeDoc v2 history entries associated with the local origin label.
|
||||
*/
|
||||
export const convertV1History = (oldHistory: V1HistoryEntry[]): HistoryEntryWithOrigin[] => {
|
||||
return oldHistory.map((entry) => ({
|
||||
identifier: entry.id,
|
||||
title: entry.text,
|
||||
tags: entry.tags,
|
||||
lastVisited: DateTime.fromMillis(entry.time).toISO(),
|
||||
lastVisitedAt: DateTime.fromMillis(entry.time).toISO(),
|
||||
pinStatus: entry.pinned,
|
||||
origin: HistoryEntryOrigin.LOCAL
|
||||
}))
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the history redux state by reloading the local history and fetching the remote history if the user is logged-in.
|
||||
*/
|
||||
export const refreshHistoryState = async (): Promise<void> => {
|
||||
const localEntries = loadLocalHistory()
|
||||
if (!getGlobalState().user) {
|
||||
|
@ -138,10 +177,16 @@ export const refreshHistoryState = async (): Promise<void> => {
|
|||
setHistoryEntries(allEntries)
|
||||
}
|
||||
|
||||
/**
|
||||
* Refreshes the history state and shows an error in case of failure.
|
||||
*/
|
||||
export const safeRefreshHistoryState = (): void => {
|
||||
refreshHistoryState().catch(showErrorNotification('landing.history.error.getHistory.text'))
|
||||
}
|
||||
|
||||
/**
|
||||
* Stores the history entries marked as local from the redux to the user's local-storage.
|
||||
*/
|
||||
export const storeLocalHistory = (): void => {
|
||||
const history = getGlobalState().history
|
||||
const localEntries = history.filter((entry) => entry.origin === HistoryEntryOrigin.LOCAL)
|
||||
|
@ -152,17 +197,24 @@ export const storeLocalHistory = (): void => {
|
|||
window.localStorage.setItem('history', JSON.stringify(entriesWithoutOrigin))
|
||||
}
|
||||
|
||||
export const storeRemoteHistory = (): Promise<void> => {
|
||||
/**
|
||||
* Stores the history entries marked as remote from the redux to the server.
|
||||
*/
|
||||
export const storeRemoteHistory = (): Promise<unknown> => {
|
||||
if (!getGlobalState().user) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
const history = getGlobalState().history
|
||||
const remoteEntries = history.filter((entry) => entry.origin === HistoryEntryOrigin.REMOTE)
|
||||
const remoteEntryDtos = remoteEntries.map(historyEntryToHistoryEntryPutDto)
|
||||
return postHistory(remoteEntryDtos)
|
||||
return setRemoteHistoryEntries(remoteEntryDtos)
|
||||
}
|
||||
|
||||
const loadLocalHistory = (): HistoryEntry[] => {
|
||||
/**
|
||||
* Loads the local history from local-storage, converts from V1 format if necessary and returns the history entries with a local origin label.
|
||||
* @return The local history entries with the origin set to local.
|
||||
*/
|
||||
const loadLocalHistory = (): HistoryEntryWithOrigin[] => {
|
||||
const localV1Json = window.localStorage.getItem('notehistory')
|
||||
if (localV1Json) {
|
||||
try {
|
||||
|
@ -181,7 +233,7 @@ const loadLocalHistory = (): HistoryEntry[] => {
|
|||
}
|
||||
|
||||
try {
|
||||
const localHistory = JSON.parse(localJson) as HistoryEntry[]
|
||||
const localHistory = JSON.parse(localJson) as HistoryEntryWithOrigin[]
|
||||
localHistory.forEach((entry) => {
|
||||
entry.origin = HistoryEntryOrigin.LOCAL
|
||||
})
|
||||
|
@ -192,10 +244,14 @@ const loadLocalHistory = (): HistoryEntry[] => {
|
|||
}
|
||||
}
|
||||
|
||||
const loadRemoteHistory = async (): Promise<HistoryEntry[]> => {
|
||||
/**
|
||||
* Loads the remote history and maps each entry with a remote origin label.
|
||||
* @return The remote history entries with the origin set to remote.
|
||||
*/
|
||||
const loadRemoteHistory = async (): Promise<HistoryEntryWithOrigin[]> => {
|
||||
try {
|
||||
const remoteHistory = await getHistory()
|
||||
return remoteHistory.map(historyEntryDtoToHistoryEntry)
|
||||
const remoteHistory = await getRemoteHistory()
|
||||
return remoteHistory.map(addRemoteOriginToHistoryEntry)
|
||||
} catch (error) {
|
||||
log.error('Error while fetching history entries from server', error)
|
||||
return []
|
||||
|
|
|
@ -5,15 +5,16 @@
|
|||
*/
|
||||
|
||||
import type { Reducer } from 'redux'
|
||||
import type { HistoryActions, HistoryEntry } from './types'
|
||||
import type { HistoryActions } from './types'
|
||||
import { HistoryActionType } from './types'
|
||||
import type { HistoryEntryWithOrigin } from '../../api/history/types'
|
||||
|
||||
// Q: Why is the reducer initialized with an empty array instead of the actual history entries like in the config reducer?
|
||||
// A: The history reducer will be created without entries because of async entry retrieval.
|
||||
// Entries will be added after reducer initialization.
|
||||
|
||||
export const HistoryReducer: Reducer<HistoryEntry[], HistoryActions> = (
|
||||
state: HistoryEntry[] = [],
|
||||
export const HistoryReducer: Reducer<HistoryEntryWithOrigin[], HistoryActions> = (
|
||||
state: HistoryEntryWithOrigin[] = [],
|
||||
action: HistoryActions
|
||||
) => {
|
||||
switch (action.type) {
|
||||
|
|
|
@ -5,20 +5,7 @@
|
|||
*/
|
||||
|
||||
import type { Action } from 'redux'
|
||||
|
||||
export enum HistoryEntryOrigin {
|
||||
LOCAL,
|
||||
REMOTE
|
||||
}
|
||||
|
||||
export interface HistoryEntry {
|
||||
identifier: string
|
||||
title: string
|
||||
lastVisited: string
|
||||
tags: string[]
|
||||
pinStatus: boolean
|
||||
origin: HistoryEntryOrigin
|
||||
}
|
||||
import type { HistoryEntryWithOrigin } from '../../api/history/types'
|
||||
|
||||
export interface V1HistoryEntry {
|
||||
id: string
|
||||
|
@ -30,7 +17,7 @@ export interface V1HistoryEntry {
|
|||
|
||||
export interface HistoryExportJson {
|
||||
version: number
|
||||
entries: HistoryEntry[]
|
||||
entries: HistoryEntryWithOrigin[]
|
||||
}
|
||||
|
||||
export enum HistoryActionType {
|
||||
|
@ -44,21 +31,21 @@ export type HistoryActions = SetEntriesAction | AddEntryAction | UpdateEntryActi
|
|||
|
||||
export interface SetEntriesAction extends Action<HistoryActionType> {
|
||||
type: HistoryActionType.SET_ENTRIES
|
||||
entries: HistoryEntry[]
|
||||
entries: HistoryEntryWithOrigin[]
|
||||
}
|
||||
|
||||
export interface AddEntryAction extends Action<HistoryActionType> {
|
||||
type: HistoryActionType.ADD_ENTRY
|
||||
newEntry: HistoryEntry
|
||||
newEntry: HistoryEntryWithOrigin
|
||||
}
|
||||
|
||||
export interface UpdateEntryAction extends Action<HistoryActionType> {
|
||||
type: HistoryActionType.UPDATE_ENTRY
|
||||
noteId: string
|
||||
newEntry: HistoryEntry
|
||||
newEntry: HistoryEntryWithOrigin
|
||||
}
|
||||
|
||||
export interface RemoveEntryAction extends HistoryEntry {
|
||||
export interface RemoveEntryAction extends Action<HistoryActionType> {
|
||||
type: HistoryActionType.REMOVE_ENTRY
|
||||
noteId: string
|
||||
}
|
||||
|
|
|
@ -63,7 +63,7 @@ const buildStateFromMarkdownContentAndLines = (
|
|||
lineStartIndexes
|
||||
},
|
||||
rawFrontmatter: '',
|
||||
noteTitle: generateNoteTitle(initialState.frontmatter, state.firstHeading),
|
||||
title: generateNoteTitle(initialState.frontmatter, state.firstHeading),
|
||||
frontmatter: initialState.frontmatter,
|
||||
frontmatterRendererInfo: initialState.frontmatterRendererInfo
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ const buildStateFromFrontmatterUpdate = (
|
|||
...state,
|
||||
rawFrontmatter: frontmatterExtraction.rawText,
|
||||
frontmatter: frontmatter,
|
||||
noteTitle: generateNoteTitle(frontmatter, state.firstHeading),
|
||||
title: generateNoteTitle(frontmatter, state.firstHeading),
|
||||
frontmatterRendererInfo: {
|
||||
lineOffset: frontmatterExtraction.lineOffset,
|
||||
deprecatedSyntax: frontmatter.deprecatedTagsSyntax,
|
||||
|
@ -100,7 +100,7 @@ const buildStateFromFrontmatterUpdate = (
|
|||
} catch (e) {
|
||||
return {
|
||||
...state,
|
||||
noteTitle: generateNoteTitle(initialState.frontmatter, state.firstHeading),
|
||||
title: generateNoteTitle(initialState.frontmatter, state.firstHeading),
|
||||
rawFrontmatter: frontmatterExtraction.rawText,
|
||||
frontmatter: initialState.frontmatter,
|
||||
frontmatterRendererInfo: {
|
||||
|
|
|
@ -18,6 +18,9 @@ export const initialSlideOptions: SlideOptions = {
|
|||
}
|
||||
|
||||
export const initialState: NoteDetails = {
|
||||
updatedAt: DateTime.fromSeconds(0),
|
||||
updateUsername: null,
|
||||
version: 0,
|
||||
markdownContent: {
|
||||
plain: '',
|
||||
lines: [],
|
||||
|
@ -32,15 +35,17 @@ export const initialState: NoteDetails = {
|
|||
slideOptions: initialSlideOptions
|
||||
},
|
||||
id: '',
|
||||
createTime: DateTime.fromSeconds(0),
|
||||
lastChange: {
|
||||
timestamp: DateTime.fromSeconds(0),
|
||||
username: ''
|
||||
createdAt: DateTime.fromSeconds(0),
|
||||
aliases: [],
|
||||
primaryAddress: '',
|
||||
permissions: {
|
||||
owner: null,
|
||||
sharedToGroups: [],
|
||||
sharedToUsers: []
|
||||
},
|
||||
alias: '',
|
||||
viewCount: 0,
|
||||
authorship: [],
|
||||
noteTitle: '',
|
||||
editedBy: [],
|
||||
title: '',
|
||||
firstHeading: '',
|
||||
frontmatter: {
|
||||
title: '',
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { store } from '..'
|
||||
import type { NoteDto } from '../../api/notes/types'
|
||||
import type { Note, NotePermissions } from '../../api/notes/types'
|
||||
import type {
|
||||
AddTableAtCursorAction,
|
||||
FormatSelectionAction,
|
||||
|
@ -14,6 +14,7 @@ import type {
|
|||
ReplaceInMarkdownContentAction,
|
||||
SetNoteDetailsFromServerAction,
|
||||
SetNoteDocumentContentAction,
|
||||
SetNotePermissionsFromServerAction,
|
||||
UpdateCursorPositionAction,
|
||||
UpdateNoteTitleByFirstHeadingAction,
|
||||
UpdateTaskListCheckboxAction
|
||||
|
@ -36,13 +37,24 @@ export const setNoteContent = (content: string): void => {
|
|||
* Sets the note metadata for the current note from an API response DTO to the redux.
|
||||
* @param apiResponse The NoteDTO received from the API to store into redux.
|
||||
*/
|
||||
export const setNoteDataFromServer = (apiResponse: NoteDto): void => {
|
||||
export const setNoteDataFromServer = (apiResponse: Note): void => {
|
||||
store.dispatch({
|
||||
type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER,
|
||||
dto: apiResponse
|
||||
noteFromServer: apiResponse
|
||||
} as SetNoteDetailsFromServerAction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the note permissions for the current note from an API response DTO to the redux.
|
||||
* @param apiResponse The NotePermissionsDTO received from the API to store into redux.
|
||||
*/
|
||||
export const setNotePermissionsFromServer = (apiResponse: NotePermissions): void => {
|
||||
store.dispatch({
|
||||
type: NoteDetailsActionType.SET_NOTE_PERMISSIONS_FROM_SERVER,
|
||||
notePermissionsFromServer: apiResponse
|
||||
} as SetNotePermissionsFromServerAction)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the note title in the redux by the first heading found in the markdown content.
|
||||
* @param firstHeading The content of the first heading found in the markdown content.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -18,6 +18,7 @@ import { buildStateFromReplaceSelection } from './reducers/build-state-from-repl
|
|||
import { buildStateFromTaskListUpdate } from './reducers/build-state-from-task-list-update'
|
||||
import { buildStateFromSelectionFormat } from './reducers/build-state-from-selection-format'
|
||||
import { buildStateFromReplaceInMarkdownContent } from './reducers/build-state-from-replace-in-markdown-content'
|
||||
import { buildStateFromServerPermissions } from './reducers/build-state-from-server-permissions'
|
||||
|
||||
export const NoteDetailsReducer: Reducer<NoteDetails, NoteDetailsActions> = (
|
||||
state: NoteDetails = initialState,
|
||||
|
@ -28,10 +29,12 @@ export const NoteDetailsReducer: Reducer<NoteDetails, NoteDetailsActions> = (
|
|||
return buildStateFromUpdateCursorPosition(state, action.selection)
|
||||
case NoteDetailsActionType.SET_DOCUMENT_CONTENT:
|
||||
return buildStateFromUpdatedMarkdownContent(state, action.content)
|
||||
case NoteDetailsActionType.SET_NOTE_PERMISSIONS_FROM_SERVER:
|
||||
return buildStateFromServerPermissions(state, action.notePermissionsFromServer)
|
||||
case NoteDetailsActionType.UPDATE_NOTE_TITLE_BY_FIRST_HEADING:
|
||||
return buildStateFromFirstHeadingUpdate(state, action.firstHeading)
|
||||
case NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER:
|
||||
return buildStateFromServerDto(action.dto)
|
||||
return buildStateFromServerDto(action.noteFromServer)
|
||||
case NoteDetailsActionType.UPDATE_TASK_LIST_CHECKBOX:
|
||||
return buildStateFromTaskListUpdate(state, action.changedLine, action.checkboxChecked)
|
||||
case NoteDetailsActionType.REPLACE_IN_MARKDOWN_CONTENT:
|
||||
|
|
|
@ -19,8 +19,8 @@ describe('build state from first heading update', () => {
|
|||
})
|
||||
|
||||
it('generates a new state with the given first heading', () => {
|
||||
const startState = { ...initialState, firstHeading: 'heading', noteTitle: 'noteTitle' }
|
||||
const startState = { ...initialState, firstHeading: 'heading', title: 'noteTitle' }
|
||||
const actual = buildStateFromFirstHeadingUpdate(startState, 'new first heading')
|
||||
expect(actual).toStrictEqual({ ...initialState, firstHeading: 'new first heading', noteTitle: 'generated title' })
|
||||
expect(actual).toStrictEqual({ ...initialState, firstHeading: 'new first heading', title: 'generated title' })
|
||||
})
|
||||
})
|
||||
|
|
|
@ -17,6 +17,6 @@ export const buildStateFromFirstHeadingUpdate = (state: NoteDetails, firstHeadin
|
|||
return {
|
||||
...state,
|
||||
firstHeading: firstHeading,
|
||||
noteTitle: generateNoteTitle(state.frontmatter, firstHeading)
|
||||
title: generateNoteTitle(state.frontmatter, firstHeading)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { initialState } from '../initial-state'
|
||||
import type { NotePermissions } from '../../../api/notes/types'
|
||||
import { buildStateFromServerPermissions } from './build-state-from-server-permissions'
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
|
||||
describe('build state from server permissions', () => {
|
||||
it('creates a new state with the given permissions', () => {
|
||||
const state: NoteDetails = { ...initialState }
|
||||
const permissions: NotePermissions = {
|
||||
owner: 'test-owner',
|
||||
sharedToUsers: [
|
||||
{
|
||||
username: 'test-user',
|
||||
canEdit: true
|
||||
}
|
||||
],
|
||||
sharedToGroups: [
|
||||
{
|
||||
groupName: 'test-group',
|
||||
canEdit: false
|
||||
}
|
||||
]
|
||||
}
|
||||
expect(buildStateFromServerPermissions(state, permissions)).toStrictEqual({ ...state, permissions: permissions })
|
||||
})
|
||||
})
|
|
@ -0,0 +1,22 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import type { NotePermissions } from '../../../api/notes/types'
|
||||
|
||||
/**
|
||||
* Builds the updated state from a given previous state and updated NotePermissions data.
|
||||
* @param state The previous note details state.
|
||||
* @param serverPermissions The updated NotePermissions data.
|
||||
*/
|
||||
export const buildStateFromServerPermissions = (
|
||||
state: NoteDetails,
|
||||
serverPermissions: NotePermissions
|
||||
): NoteDetails => {
|
||||
return {
|
||||
...state,
|
||||
permissions: serverPermissions
|
||||
}
|
||||
}
|
|
@ -3,8 +3,6 @@
|
|||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { NoteDto } from '../../../api/notes/types'
|
||||
import { buildStateFromServerDto } from './build-state-from-set-note-data-from-server'
|
||||
import * as buildStateFromUpdatedMarkdownContentModule from '../build-state-from-updated-markdown-content'
|
||||
import { Mock } from 'ts-mockery'
|
||||
|
@ -12,6 +10,7 @@ import type { NoteDetails } from '../types/note-details'
|
|||
import { NoteTextDirection, NoteType } from '../types/note-details'
|
||||
import { DateTime } from 'luxon'
|
||||
import { initialSlideOptions } from '../initial-state'
|
||||
import type { Note } from '../../../api/notes/types'
|
||||
|
||||
describe('build state from set note data from server', () => {
|
||||
const buildStateFromUpdatedMarkdownContentMock = jest.spyOn(
|
||||
|
@ -29,54 +28,42 @@ describe('build state from set note data from server', () => {
|
|||
})
|
||||
|
||||
it('builds a new state from the given note dto', () => {
|
||||
const noteDto: NoteDto = {
|
||||
const noteDto: Note = {
|
||||
content: 'line1\nline2',
|
||||
metadata: {
|
||||
primaryAddress: 'alias',
|
||||
version: 5678,
|
||||
alias: 'alias',
|
||||
aliases: [
|
||||
{
|
||||
noteId: 'id',
|
||||
primaryAlias: true,
|
||||
name: 'alias'
|
||||
}
|
||||
],
|
||||
id: 'id',
|
||||
createTime: '2012-05-25T09:08:34.123',
|
||||
createdAt: '2012-05-25T09:08:34.123',
|
||||
description: 'description',
|
||||
editedBy: ['editedBy'],
|
||||
permissions: {
|
||||
owner: {
|
||||
username: 'username',
|
||||
photo: 'photo',
|
||||
email: 'email',
|
||||
displayName: 'displayName'
|
||||
},
|
||||
owner: 'username',
|
||||
sharedToGroups: [
|
||||
{
|
||||
canEdit: true,
|
||||
group: {
|
||||
displayName: 'groupdisplayname',
|
||||
name: 'groupname',
|
||||
special: true
|
||||
}
|
||||
groupName: 'groupName'
|
||||
}
|
||||
],
|
||||
sharedToUsers: [
|
||||
{
|
||||
canEdit: true,
|
||||
user: {
|
||||
username: 'shareusername',
|
||||
email: 'shareemail',
|
||||
photo: 'sharephoto',
|
||||
displayName: 'sharedisplayname'
|
||||
}
|
||||
username: 'shareusername'
|
||||
}
|
||||
]
|
||||
},
|
||||
viewCount: 987,
|
||||
tags: ['tag'],
|
||||
title: 'title',
|
||||
updateTime: '2020-05-25T09:08:34.123',
|
||||
updateUser: {
|
||||
username: 'updateusername',
|
||||
photo: 'updatephoto',
|
||||
email: 'updateemail',
|
||||
displayName: 'updatedisplayname'
|
||||
}
|
||||
updatedAt: '2020-05-25T09:08:34.123',
|
||||
updateUsername: 'updateusername'
|
||||
},
|
||||
editedByAtPosition: [
|
||||
{
|
||||
|
@ -84,7 +71,7 @@ describe('build state from set note data from server', () => {
|
|||
createdAt: 'createdAt',
|
||||
startPos: 9,
|
||||
updatedAt: 'updatedAt',
|
||||
userName: 'userName'
|
||||
username: 'userName'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -117,7 +104,7 @@ describe('build state from set note data from server', () => {
|
|||
lineOffset: 0,
|
||||
slideOptions: initialSlideOptions
|
||||
},
|
||||
noteTitle: '',
|
||||
title: 'title',
|
||||
selection: { from: 0 },
|
||||
markdownContent: {
|
||||
plain: 'line1\nline2',
|
||||
|
@ -127,14 +114,35 @@ describe('build state from set note data from server', () => {
|
|||
firstHeading: '',
|
||||
rawFrontmatter: '',
|
||||
id: 'id',
|
||||
createTime: DateTime.fromISO('2012-05-25T09:08:34.123'),
|
||||
lastChange: {
|
||||
username: 'updateusername',
|
||||
timestamp: DateTime.fromISO('2020-05-25T09:08:34.123')
|
||||
},
|
||||
createdAt: DateTime.fromISO('2012-05-25T09:08:34.123'),
|
||||
updatedAt: DateTime.fromISO('2020-05-25T09:08:34.123'),
|
||||
updateUsername: 'updateusername',
|
||||
viewCount: 987,
|
||||
alias: 'alias',
|
||||
authorship: ['editedBy']
|
||||
aliases: [
|
||||
{
|
||||
name: 'alias',
|
||||
noteId: 'id',
|
||||
primaryAlias: true
|
||||
}
|
||||
],
|
||||
primaryAddress: 'alias',
|
||||
version: 5678,
|
||||
editedBy: ['editedBy'],
|
||||
permissions: {
|
||||
owner: 'username',
|
||||
sharedToGroups: [
|
||||
{
|
||||
canEdit: true,
|
||||
groupName: 'groupName'
|
||||
}
|
||||
],
|
||||
sharedToUsers: [
|
||||
{
|
||||
canEdit: true,
|
||||
username: 'shareusername'
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
const result = buildStateFromServerDto(noteDto)
|
||||
|
|
|
@ -4,19 +4,19 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { NoteDto } from '../../../api/notes/types'
|
||||
import type { NoteDetails } from '../types/note-details'
|
||||
import { buildStateFromUpdatedMarkdownContent } from '../build-state-from-updated-markdown-content'
|
||||
import { initialState } from '../initial-state'
|
||||
import { DateTime } from 'luxon'
|
||||
import { calculateLineStartIndexes } from '../calculate-line-start-indexes'
|
||||
import type { Note } from '../../../api/notes/types'
|
||||
|
||||
/**
|
||||
* Builds a {@link NoteDetails} redux state from a DTO received as an API response.
|
||||
* @param dto The first DTO received from the API containing the relevant information about the note.
|
||||
* @return An updated {@link NoteDetails} redux state.
|
||||
*/
|
||||
export const buildStateFromServerDto = (dto: NoteDto): NoteDetails => {
|
||||
export const buildStateFromServerDto = (dto: Note): NoteDetails => {
|
||||
const newState = convertNoteDtoToNoteDetails(dto)
|
||||
return buildStateFromUpdatedMarkdownContent(newState, newState.markdownContent.plain)
|
||||
}
|
||||
|
@ -27,24 +27,26 @@ export const buildStateFromServerDto = (dto: NoteDto): NoteDetails => {
|
|||
* @param note The NoteDTO as defined in the backend.
|
||||
* @return The NoteDetails object corresponding to the DTO.
|
||||
*/
|
||||
const convertNoteDtoToNoteDetails = (note: NoteDto): NoteDetails => {
|
||||
const convertNoteDtoToNoteDetails = (note: Note): NoteDetails => {
|
||||
const newLines = note.content.split('\n')
|
||||
return {
|
||||
...initialState,
|
||||
updateUsername: note.metadata.updateUsername,
|
||||
permissions: note.metadata.permissions,
|
||||
editedBy: note.metadata.editedBy,
|
||||
primaryAddress: note.metadata.primaryAddress,
|
||||
id: note.metadata.id,
|
||||
aliases: note.metadata.aliases,
|
||||
title: note.metadata.title,
|
||||
version: note.metadata.version,
|
||||
viewCount: note.metadata.viewCount,
|
||||
markdownContent: {
|
||||
plain: note.content,
|
||||
lines: newLines,
|
||||
lineStartIndexes: calculateLineStartIndexes(newLines)
|
||||
},
|
||||
rawFrontmatter: '',
|
||||
id: note.metadata.id,
|
||||
createTime: DateTime.fromISO(note.metadata.createTime),
|
||||
lastChange: {
|
||||
username: note.metadata.updateUser.username,
|
||||
timestamp: DateTime.fromISO(note.metadata.updateTime)
|
||||
},
|
||||
viewCount: note.metadata.viewCount,
|
||||
alias: note.metadata.alias,
|
||||
authorship: note.metadata.editedBy
|
||||
createdAt: DateTime.fromISO(note.metadata.createdAt),
|
||||
updatedAt: DateTime.fromISO(note.metadata.updatedAt)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { Action } from 'redux'
|
||||
import type { NoteDto } from '../../api/notes/types'
|
||||
import type { Note, NotePermissions } from '../../api/notes/types'
|
||||
import type { CursorSelection } from '../editor/types'
|
||||
|
||||
export enum NoteDetailsActionType {
|
||||
SET_DOCUMENT_CONTENT = 'note-details/content/set',
|
||||
SET_NOTE_DATA_FROM_SERVER = 'note-details/data/server/set',
|
||||
SET_NOTE_PERMISSIONS_FROM_SERVER = 'note-details/data/permissions/set',
|
||||
UPDATE_NOTE_TITLE_BY_FIRST_HEADING = 'note-details/update-note-title-by-first-heading',
|
||||
UPDATE_TASK_LIST_CHECKBOX = 'note-details/update-task-list-checkbox',
|
||||
UPDATE_CURSOR_POSITION = 'note-details/updateCursorPosition',
|
||||
|
@ -44,6 +45,7 @@ export enum FormatType {
|
|||
export type NoteDetailsActions =
|
||||
| SetNoteDocumentContentAction
|
||||
| SetNoteDetailsFromServerAction
|
||||
| SetNotePermissionsFromServerAction
|
||||
| UpdateNoteTitleByFirstHeadingAction
|
||||
| UpdateTaskListCheckboxAction
|
||||
| UpdateCursorPositionAction
|
||||
|
@ -65,7 +67,15 @@ export interface SetNoteDocumentContentAction extends Action<NoteDetailsActionTy
|
|||
*/
|
||||
export interface SetNoteDetailsFromServerAction extends Action<NoteDetailsActionType> {
|
||||
type: NoteDetailsActionType.SET_NOTE_DATA_FROM_SERVER
|
||||
dto: NoteDto
|
||||
noteFromServer: Note
|
||||
}
|
||||
|
||||
/**
|
||||
* Action for overwriting the current permission state with the data received from the API.
|
||||
*/
|
||||
export interface SetNotePermissionsFromServerAction extends Action<NoteDetailsActionType> {
|
||||
type: NoteDetailsActionType.SET_NOTE_PERMISSIONS_FROM_SERVER
|
||||
notePermissionsFromServer: NotePermissions
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -8,31 +8,26 @@ import type { DateTime } from 'luxon'
|
|||
import type { SlideOptions } from './slide-show-options'
|
||||
import type { ISO6391 } from './iso6391'
|
||||
import type { CursorSelection } from '../../editor/types'
|
||||
import type { NoteMetadata } from '../../../api/notes/types'
|
||||
|
||||
type UnnecessaryNoteAttributes = 'updatedAt' | 'createdAt' | 'tags' | 'description'
|
||||
|
||||
/**
|
||||
* Redux state containing the currently loaded note with its content and metadata.
|
||||
*/
|
||||
export interface NoteDetails {
|
||||
export interface NoteDetails extends Omit<NoteMetadata, UnnecessaryNoteAttributes> {
|
||||
updatedAt: DateTime
|
||||
createdAt: DateTime
|
||||
markdownContent: {
|
||||
plain: string
|
||||
lines: string[]
|
||||
lineStartIndexes: number[]
|
||||
}
|
||||
selection: CursorSelection
|
||||
firstHeading?: string
|
||||
rawFrontmatter: string
|
||||
frontmatter: NoteFrontmatter
|
||||
frontmatterRendererInfo: RendererFrontmatterInfo
|
||||
id: string
|
||||
createTime: DateTime
|
||||
lastChange: {
|
||||
username: string
|
||||
timestamp: DateTime
|
||||
}
|
||||
viewCount: number
|
||||
alias: string
|
||||
authorship: string[]
|
||||
noteTitle: string
|
||||
firstHeading?: string
|
||||
}
|
||||
|
||||
export type Iso6391Language = typeof ISO6391[number]
|
||||
|
|
|
@ -9,7 +9,6 @@ import { combineReducers } from 'redux'
|
|||
import { UserReducer } from './user/reducers'
|
||||
import { ConfigReducer } from './config/reducers'
|
||||
import { MotdReducer } from './motd/reducers'
|
||||
import { ApiUrlReducer } from './api-url/reducers'
|
||||
import { HistoryReducer } from './history/reducers'
|
||||
import { EditorConfigReducer } from './editor/reducers'
|
||||
import { DarkModeConfigReducer } from './dark-mode/reducers'
|
||||
|
@ -22,7 +21,6 @@ export const allReducers: Reducer<ApplicationState> = combineReducers<Applicatio
|
|||
user: UserReducer,
|
||||
config: ConfigReducer,
|
||||
motd: MotdReducer,
|
||||
apiUrl: ApiUrlReducer,
|
||||
history: HistoryReducer,
|
||||
editorConfig: EditorConfigReducer,
|
||||
darkMode: DarkModeConfigReducer,
|
||||
|
|
|
@ -5,10 +5,15 @@
|
|||
*/
|
||||
|
||||
import { store } from '..'
|
||||
import type { ClearUserAction, SetUserAction, UserState } from './types'
|
||||
import type { ClearUserAction, SetUserAction } from './types'
|
||||
import { UserActionType } from './types'
|
||||
import type { LoginUserInfo } from '../../api/me/types'
|
||||
|
||||
export const setUser: (state: UserState) => void = (state: UserState) => {
|
||||
/**
|
||||
* Sets the given user state into the redux.
|
||||
* @param state The user state to set into the redux.
|
||||
*/
|
||||
export const setUser = (state: LoginUserInfo): void => {
|
||||
const action: SetUserAction = {
|
||||
type: UserActionType.SET_USER,
|
||||
state
|
||||
|
@ -16,6 +21,9 @@ export const setUser: (state: UserState) => void = (state: UserState) => {
|
|||
store.dispatch(action)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the user state from the redux.
|
||||
*/
|
||||
export const clearUser: () => void = () => {
|
||||
const action: ClearUserAction = {
|
||||
type: UserActionType.CLEAR_USER
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
*/
|
||||
|
||||
import type { Action } from 'redux'
|
||||
import type { LoginUserInfo } from '../../api/me/types'
|
||||
|
||||
export enum UserActionType {
|
||||
SET_USER = 'user/set',
|
||||
|
@ -15,32 +16,11 @@ export type UserActions = SetUserAction | ClearUserAction
|
|||
|
||||
export interface SetUserAction extends Action<UserActionType> {
|
||||
type: UserActionType.SET_USER
|
||||
state: UserState
|
||||
state: LoginUserInfo
|
||||
}
|
||||
|
||||
export interface ClearUserAction extends Action<UserActionType> {
|
||||
type: UserActionType.CLEAR_USER
|
||||
}
|
||||
|
||||
export interface UserState {
|
||||
username: string
|
||||
displayName: string
|
||||
email: string
|
||||
photo: string
|
||||
provider: LoginProvider
|
||||
}
|
||||
|
||||
export enum LoginProvider {
|
||||
FACEBOOK = 'facebook',
|
||||
GITHUB = 'github',
|
||||
TWITTER = 'twitter',
|
||||
GITLAB = 'gitlab',
|
||||
DROPBOX = 'dropbox',
|
||||
GOOGLE = 'google',
|
||||
SAML = 'saml',
|
||||
OAUTH2 = 'oauth2',
|
||||
LOCAL = 'local',
|
||||
LDAP = 'ldap'
|
||||
}
|
||||
|
||||
export type OptionalUserState = UserState | null
|
||||
export type OptionalUserState = LoginUserInfo | null
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue