mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-25 12:34:45 -04:00
feat(explore): API methods for asking backend for explore page notes
Co-authored-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Philip Molares <philip.molares@udo.edu> Signed-off-by: Erik Michelson <github@erik.michelson.eu>
This commit is contained in:
parent
f645cffcd5
commit
a60c7a6e10
6 changed files with 238 additions and 71 deletions
79
frontend/src/api/explore/index.ts
Normal file
79
frontend/src/api/explore/index.ts
Normal file
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { NoteEntry } from './types'
|
||||
import { GetApiRequestBuilder } from '../common/api-request-builder/get-api-request-builder'
|
||||
import type { SortMode } from '../../components/explore-page/explore-notes-section/filters/sort-button'
|
||||
import type { NoteType } from '@hedgedoc/commons'
|
||||
import { createURLSearchParams } from './utils'
|
||||
import { mockNotes } from './mock'
|
||||
|
||||
/**
|
||||
* Fetches the pinned notes of a user
|
||||
*
|
||||
* @return A list of pinned notes.
|
||||
* @throws {Error} when the api request wasn't successful.
|
||||
*/
|
||||
export const getPinnedNotes = async (): Promise<NoteEntry[]> => {
|
||||
return mockNotes.filter((note) => note.isPinned)
|
||||
const response = await new GetApiRequestBuilder<NoteEntry[]>('explore/pinned').sendRequest()
|
||||
return response.asParsedJsonObject()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the notes of the logged-in user for the explore page
|
||||
*
|
||||
* @return The notes of the logged-in user.
|
||||
* @throws {Error} when the api request wasn't successful.
|
||||
*/
|
||||
export const getMyNotes = async (
|
||||
sort: SortMode,
|
||||
searchFilter: string | null,
|
||||
typeFilter: NoteType | null
|
||||
): Promise<NoteEntry[]> => {
|
||||
return mockNotes
|
||||
const params = createURLSearchParams(sort, searchFilter, typeFilter)
|
||||
const response = await new GetApiRequestBuilder<NoteEntry[]>(`explore/my?${params}`).sendRequest()
|
||||
return response.asParsedJsonObject()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the notes shared with the logged-in user for the explore page
|
||||
*
|
||||
* @return The notes shared with the logged-in user.
|
||||
* @throws {Error} when the api request wasn't successful.
|
||||
*/
|
||||
export const getSharedNotes = async (
|
||||
sort: SortMode,
|
||||
searchFilter: string | null,
|
||||
typeFilter: NoteType | null
|
||||
): Promise<NoteEntry[]> => {
|
||||
return mockNotes
|
||||
const params = createURLSearchParams(sort, searchFilter, typeFilter)
|
||||
const response = await new GetApiRequestBuilder<NoteEntry[]>(`explore/shared?${params}`).sendRequest()
|
||||
return response.asParsedJsonObject()
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches the public notes of the instance
|
||||
*
|
||||
* @return A list of public notes.
|
||||
* @throws {Error} when the api request wasn't successful.
|
||||
*/
|
||||
export const getPublicNotes = async (
|
||||
sort: SortMode,
|
||||
searchFilter: string | null,
|
||||
typeFilter: NoteType | null
|
||||
): Promise<NoteEntry[]> => {
|
||||
return mockNotes
|
||||
const params = createURLSearchParams(sort, searchFilter, typeFilter)
|
||||
const response = await new GetApiRequestBuilder<NoteEntry[]>(`explore/public?${params}`).sendRequest()
|
||||
return response.asParsedJsonObject()
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
60
frontend/src/api/explore/mock.ts
Normal file
60
frontend/src/api/explore/mock.ts
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
// TODO Remove mock entries as soon as the backend is implemented
|
||||
import type { NoteEntry } from './types'
|
||||
import { NoteType } from '@hedgedoc/commons'
|
||||
|
||||
export const mockNotes: NoteEntry[] = [
|
||||
{
|
||||
primaryAddress: 'othermo',
|
||||
title: 'othermo Backend / Fullstack Dev',
|
||||
type: NoteType.DOCUMENT,
|
||||
isPinned: true,
|
||||
lastChangedAt: new Date(2025, 0, 2, 14, 0, 0).toISOString(),
|
||||
tags: ['Arbeit', 'Ausschreibung']
|
||||
},
|
||||
{
|
||||
primaryAddress: 'ev',
|
||||
title: 'HedgeDoc e.V.',
|
||||
type: NoteType.DOCUMENT,
|
||||
isPinned: false,
|
||||
lastChangedAt: new Date(2025, 0, 13, 14, 0, 0).toISOString(),
|
||||
tags: ['HedgeDoc', 'Verein']
|
||||
},
|
||||
{
|
||||
primaryAddress: 'sister-projects',
|
||||
title: 'Sister projects of HedgeDoc for the future',
|
||||
type: NoteType.DOCUMENT,
|
||||
isPinned: false,
|
||||
lastChangedAt: new Date(2025, 0, 13, 14, 0, 0).toISOString(),
|
||||
tags: ['HedgeDoc', 'Funny']
|
||||
},
|
||||
{
|
||||
primaryAddress: 'keynote',
|
||||
title: 'HedgeDoc Keynote',
|
||||
type: NoteType.SLIDE,
|
||||
isPinned: false,
|
||||
lastChangedAt: new Date(2025, 0, 13, 14, 0, 0).toISOString(),
|
||||
tags: []
|
||||
},
|
||||
{
|
||||
primaryAddress: '5',
|
||||
title: 'KIF-Admin KIF 47,5',
|
||||
type: NoteType.DOCUMENT,
|
||||
isPinned: false,
|
||||
lastChangedAt: new Date(2020, 2, 13, 14, 0, 0).toISOString(),
|
||||
tags: ['KIF-Admin', 'KIF 47,5']
|
||||
},
|
||||
{
|
||||
primaryAddress: 'wifionice',
|
||||
title: 'kif.rocks vs WifiOnICE/Bahn WLAN',
|
||||
type: NoteType.DOCUMENT,
|
||||
isPinned: false,
|
||||
lastChangedAt: new Date(2020, 0, 13, 14, 0, 0).toISOString(),
|
||||
tags: ['Privat', 'Blogpost']
|
||||
}
|
||||
]
|
17
frontend/src/api/explore/types.ts
Normal file
17
frontend/src/api/explore/types.ts
Normal file
|
@ -0,0 +1,17 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { NoteType } from '@hedgedoc/commons'
|
||||
|
||||
export interface NoteEntry {
|
||||
primaryAddress: string
|
||||
title: string
|
||||
type: NoteType
|
||||
tags: string[]
|
||||
owner: string | null
|
||||
isPinned: boolean
|
||||
lastChangedAt: string
|
||||
}
|
36
frontend/src/api/explore/utils.spec.ts
Normal file
36
frontend/src/api/explore/utils.spec.ts
Normal file
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { SortMode } from '../../components/explore-page/explore-notes-section/filters/sort-button'
|
||||
import { NoteType } from '@hedgedoc/commons'
|
||||
import { createURLSearchParams } from './utils'
|
||||
|
||||
describe('createURLSearchParams', () => {
|
||||
it('only sort is defined', () => {
|
||||
const sort = SortMode.TITLE_ASC
|
||||
const result = createURLSearchParams(sort, null, null)
|
||||
expect(result).toStrictEqual('sort=title_asc')
|
||||
})
|
||||
it('sort and search are defined', () => {
|
||||
const sort = SortMode.TITLE_ASC
|
||||
const search = 'test'
|
||||
const result = createURLSearchParams(sort, search, null)
|
||||
expect(result).toStrictEqual('sort=title_asc&search=test')
|
||||
})
|
||||
it('sort and type are defined', () => {
|
||||
const sort = SortMode.TITLE_ASC
|
||||
const type = NoteType.DOCUMENT
|
||||
const result = createURLSearchParams(sort, null, type)
|
||||
expect(result).toStrictEqual('sort=title_asc&type=document')
|
||||
})
|
||||
it('everything is defined', () => {
|
||||
const sort = SortMode.TITLE_ASC
|
||||
const search = 'test'
|
||||
const type = NoteType.DOCUMENT
|
||||
const result = createURLSearchParams(sort, search, type)
|
||||
expect(result).toStrictEqual('sort=title_asc&search=test&type=document')
|
||||
})
|
||||
})
|
31
frontend/src/api/explore/utils.ts
Normal file
31
frontend/src/api/explore/utils.ts
Normal file
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { SortMode } from '../../components/explore-page/explore-notes-section/filters/sort-button'
|
||||
import type { NoteType } from '@hedgedoc/commons'
|
||||
|
||||
/**
|
||||
* Create the necessary url parameters for the api calls of the explore page.
|
||||
* @param sort
|
||||
* @param searchFilter
|
||||
* @param typeFilter
|
||||
* @return a string representation of the search parameter
|
||||
*/
|
||||
export const createURLSearchParams = (
|
||||
sort: SortMode,
|
||||
searchFilter: string | null,
|
||||
typeFilter: NoteType | null
|
||||
): string => {
|
||||
const params = new URLSearchParams()
|
||||
params.set('sort', sort)
|
||||
if (searchFilter) {
|
||||
params.set('search', searchFilter)
|
||||
}
|
||||
if (typeFilter) {
|
||||
params.set('type', typeFilter)
|
||||
}
|
||||
return params.toString()
|
||||
}
|
|
@ -4,75 +4,14 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React, { Fragment, useCallback, useEffect, useMemo, useRef, useState } from 'react'
|
||||
import type { NoteCardProps } from './pinned-note-card'
|
||||
import { PinnedNoteCard } from './pinned-note-card'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { NoteType } from '@hedgedoc/commons'
|
||||
import { Caret } from './caret'
|
||||
import styles from './pinned-notes.module.css'
|
||||
|
||||
const mockListPinnedNotes: NoteCardProps[] = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'othermo Backend / Fullstack Dev',
|
||||
type: NoteType.DOCUMENT,
|
||||
pinned: true,
|
||||
lastVisited: new Date(2025, 0, 2, 14, 0, 0).toISOString(),
|
||||
created: new Date(2025, 0, 1, 12, 0, 0).toISOString(),
|
||||
tags: ['Arbeit', 'Ausschreibung'],
|
||||
primaryAddress: 'othermo'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'HedgeDoc e.V.',
|
||||
type: NoteType.DOCUMENT,
|
||||
pinned: false,
|
||||
lastVisited: new Date(2025, 0, 13, 14, 0, 0).toISOString(),
|
||||
created: new Date(2025, 0, 12, 12, 0, 0).toISOString(),
|
||||
tags: ['HedgeDoc', 'Verein'],
|
||||
primaryAddress: 'ev'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Sister projects of HedgeDoc for the future',
|
||||
type: NoteType.DOCUMENT,
|
||||
pinned: false,
|
||||
lastVisited: new Date(2025, 0, 13, 14, 0, 0).toISOString(),
|
||||
created: new Date(2025, 0, 12, 12, 0, 0).toISOString(),
|
||||
tags: ['HedgeDoc', 'Funny'],
|
||||
primaryAddress: 'sister-projects'
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
title: 'HedgeDoc Keynote',
|
||||
type: NoteType.SLIDE,
|
||||
pinned: false,
|
||||
lastVisited: new Date(2025, 0, 13, 14, 0, 0).toISOString(),
|
||||
created: new Date(2025, 0, 12, 12, 0, 0).toISOString(),
|
||||
tags: [],
|
||||
primaryAddress: 'keynote'
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
title: 'KIF-Admin KIF 47,5',
|
||||
type: NoteType.DOCUMENT,
|
||||
pinned: false,
|
||||
lastVisited: new Date(2020, 2, 13, 14, 0, 0).toISOString(),
|
||||
created: new Date(2019, 10, 12, 12, 0, 0).toISOString(),
|
||||
tags: ['KIF-Admin', 'KIF 47,5'],
|
||||
primaryAddress: '5'
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
title: 'kif.rocks vs WifiOnICE/Bahn WLAN',
|
||||
type: NoteType.DOCUMENT,
|
||||
pinned: false,
|
||||
lastVisited: new Date(2020, 0, 13, 14, 0, 0).toISOString(),
|
||||
created: new Date(2020, 0, 12, 12, 0, 0).toISOString(),
|
||||
tags: ['Privat', 'Blogpost'],
|
||||
primaryAddress: 'wifionice'
|
||||
}
|
||||
]
|
||||
import type { NoteEntry } from '../../../api/explore/types'
|
||||
import { useAsync } from 'react-use'
|
||||
import { getPinnedNotes } from '../../../api/explore'
|
||||
import { AsyncLoadingBoundary } from '../../common/async-loading-boundary/async-loading-boundary'
|
||||
|
||||
export const PinnedNotes: React.FC = () => {
|
||||
useTranslation()
|
||||
|
@ -80,9 +19,7 @@ export const PinnedNotes: React.FC = () => {
|
|||
const [enableScrollLeft, setEnableScrollLeft] = useState(false)
|
||||
const [enableScrollRight, setEnableScrollRight] = useState(true)
|
||||
|
||||
const pinnedNotes = useMemo(() => {
|
||||
return mockListPinnedNotes
|
||||
}, [])
|
||||
const { value: pinnedNotes, loading, error } = useAsync(getPinnedNotes, [])
|
||||
|
||||
const leftClick = useCallback(() => {
|
||||
if (!scrollboxRef.current) {
|
||||
|
@ -103,6 +40,13 @@ export const PinnedNotes: React.FC = () => {
|
|||
})
|
||||
}, [])
|
||||
|
||||
const pinnedNoteCards = useMemo(() => {
|
||||
if (!pinnedNotes) {
|
||||
return null
|
||||
}
|
||||
return pinnedNotes.map((note: NoteEntry) => <PinnedNoteCard key={note.primaryAddress} {...note} />)
|
||||
}, [pinnedNotes])
|
||||
|
||||
useEffect(() => {
|
||||
if (!scrollboxRef.current) {
|
||||
return
|
||||
|
@ -127,9 +71,9 @@ export const PinnedNotes: React.FC = () => {
|
|||
<div className={'d-flex flex-row gap-2 align-items-center mb-4'}>
|
||||
<Caret active={enableScrollLeft} left={true} onClick={leftClick} />
|
||||
<div className={styles.scrollbox} ref={scrollboxRef}>
|
||||
{pinnedNotes.map((note: NoteCardProps) => (
|
||||
<PinnedNoteCard key={note.id} {...note} />
|
||||
))}
|
||||
<AsyncLoadingBoundary componentName={'PinnedNotes'} loading={loading} error={error}>
|
||||
{pinnedNoteCards}
|
||||
</AsyncLoadingBoundary>
|
||||
</div>
|
||||
<Caret active={enableScrollRight} left={false} onClick={rightClick} />
|
||||
</div>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue