fix: Move content into to frontend directory

Doing this BEFORE the merge prevents a lot of merge conflicts.

Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
Tilman Vatteroth 2022-11-11 11:16:18 +01:00
parent 4e18ce38f3
commit 762a0a850e
No known key found for this signature in database
GPG key ID: B97799103358209B
1051 changed files with 0 additions and 35 deletions

View file

@ -0,0 +1,251 @@
/*
* SPDX-FileCopyrightText: 2022 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { getGlobalState, store } from '../index'
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 {
deleteRemoteHistory,
deleteRemoteHistoryEntry,
getRemoteHistory,
setRemoteHistoryEntries,
updateRemoteHistoryEntryPinStatus
} from '../../api/history'
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'
const log = new Logger('Redux > History')
/**
* 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
} as SetEntriesAction)
storeLocalHistory()
}
/**
* 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()
}
/**
* 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 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,
noteId,
newEntry
} 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 deleteRemoteHistoryEntry(noteId)
}
store.dispatch({
type: HistoryActionType.REMOVE_ENTRY,
noteId
} as RemoveEntryAction)
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)
if (!entryToUpdate) {
return Promise.reject(`History entry for note '${noteId}' not found`)
}
const updatedEntry = {
...entryToUpdate,
pinStatus: !entryToUpdate.pinStatus
}
if (entryToUpdate.origin === HistoryEntryOrigin.LOCAL) {
updateLocalHistoryEntry(noteId, updatedEntry)
} else {
await updateRemoteHistoryEntryPinStatus(noteId, updatedEntry.pinStatus)
updateHistoryEntryRedux(noteId, updatedEntry)
}
}
/**
* 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<HistoryEntryWithOrigin>) => {
delete entry.origin
})
const json = JSON.stringify({
version: 2,
entries: history
} as HistoryExportJson)
download(json, `history_${Date.now()}.json`, 'application/json')
}
/**
* 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)
}
/**
* 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,
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) {
setHistoryEntries(localEntries)
return
}
const remoteEntries = await loadRemoteHistory()
const allEntries = mergeHistoryEntries(localEntries, remoteEntries)
setHistoryEntries(allEntries)
}
/**
* 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)
const entriesWithoutOrigin = localEntries.map((entry) => ({
...entry,
origin: undefined
}))
window.localStorage.setItem('history', JSON.stringify(entriesWithoutOrigin))
}
/**
* 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 setRemoteHistoryEntries(remoteEntryDtos)
}
/**
* 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 {
const localV1History = JSON.parse(JSON.parse(localV1Json) as string) as V1HistoryEntry[]
window.localStorage.removeItem('notehistory')
return convertV1History(localV1History)
} catch (error) {
log.error('Error while converting old history entries', error)
return []
}
}
const localJson = window.localStorage.getItem('history')
if (!localJson) {
return []
}
try {
const localHistory = JSON.parse(localJson) as HistoryEntryWithOrigin[]
localHistory.forEach((entry) => {
entry.origin = HistoryEntryOrigin.LOCAL
})
return localHistory
} catch (error) {
log.error('Error while parsing locally stored history entries', error)
return []
}
}
/**
* 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 getRemoteHistory()
return remoteHistory.map(addRemoteOriginToHistoryEntry)
} catch (error) {
log.error('Error while fetching history entries from server', error)
return []
}
}

View file

@ -0,0 +1,30 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Reducer } from 'redux'
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<HistoryEntryWithOrigin[], HistoryActions> = (
state: HistoryEntryWithOrigin[] = [],
action: HistoryActions
) => {
switch (action.type) {
case HistoryActionType.SET_ENTRIES:
return action.entries
case HistoryActionType.UPDATE_ENTRY:
return [...state.filter((entry) => entry.identifier !== action.noteId), action.newEntry]
case HistoryActionType.REMOVE_ENTRY:
return state.filter((entry) => entry.identifier !== action.noteId)
default:
return state
}
}

View file

@ -0,0 +1,51 @@
/*
* SPDX-FileCopyrightText: 2021 The HedgeDoc developers (see AUTHORS file)
*
* SPDX-License-Identifier: AGPL-3.0-only
*/
import type { Action } from 'redux'
import type { HistoryEntryWithOrigin } from '../../api/history/types'
export interface V1HistoryEntry {
id: string
text: string
time: number
tags: string[]
pinned: boolean
}
export interface HistoryExportJson {
version: number
entries: HistoryEntryWithOrigin[]
}
export enum HistoryActionType {
SET_ENTRIES = 'SET_ENTRIES',
ADD_ENTRY = 'ADD_ENTRY',
UPDATE_ENTRY = 'UPDATE_ENTRY',
REMOVE_ENTRY = 'REMOVE_ENTRY'
}
export type HistoryActions = SetEntriesAction | AddEntryAction | UpdateEntryAction | RemoveEntryAction
export interface SetEntriesAction extends Action<HistoryActionType> {
type: HistoryActionType.SET_ENTRIES
entries: HistoryEntryWithOrigin[]
}
export interface AddEntryAction extends Action<HistoryActionType> {
type: HistoryActionType.ADD_ENTRY
newEntry: HistoryEntryWithOrigin
}
export interface UpdateEntryAction extends Action<HistoryActionType> {
type: HistoryActionType.UPDATE_ENTRY
noteId: string
newEntry: HistoryEntryWithOrigin
}
export interface RemoveEntryAction extends Action<HistoryActionType> {
type: HistoryActionType.REMOVE_ENTRY
noteId: string
}