mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-01 23:58:58 -04:00
feat(explore): add common explore page layout
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
055d2b6c5b
commit
869366a744
24 changed files with 525 additions and 18 deletions
|
@ -3,13 +3,26 @@
|
|||
"slogan": "Ideas grow better together",
|
||||
"icon": "HedgeDoc logo with text"
|
||||
},
|
||||
"notificationTest": {
|
||||
"title": "Test",
|
||||
"content": "It works!"
|
||||
},
|
||||
"navigation": {
|
||||
"newNote": "New Note"
|
||||
},
|
||||
"explore": {
|
||||
"title": "Explore notes",
|
||||
"welcome": {
|
||||
"user": "Welcome, {{userName}}!",
|
||||
"guest": "Welcome, guest!"
|
||||
},
|
||||
"pinnedNotes": {
|
||||
"title": "Pinned notes",
|
||||
"unpin": "Click to unpin this note",
|
||||
"empty": "You don't have any pinned notes yet."
|
||||
},
|
||||
"modes": {
|
||||
"my": "My notes",
|
||||
"shared": "Shared with me",
|
||||
"public": "Public notes"
|
||||
}
|
||||
},
|
||||
"renderer": {
|
||||
"highlightCode": {
|
||||
"copyCode": "Copy code to clipboard"
|
||||
|
|
18
frontend/src/app/(editor)/@appBar/explore/my/page.tsx
Normal file
18
frontend/src/app/(editor)/@appBar/explore/my/page.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
'use client'
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { BaseAppBar } from '../../../../../components/layout/app-bar/base-app-bar'
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export default function AppBar() {
|
||||
useTranslation()
|
||||
return (
|
||||
<BaseAppBar>
|
||||
<Trans i18nKey={'explore.modes.my'} />
|
||||
</BaseAppBar>
|
||||
)
|
||||
}
|
18
frontend/src/app/(editor)/@appBar/explore/public/page.tsx
Normal file
18
frontend/src/app/(editor)/@appBar/explore/public/page.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
'use client'
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { BaseAppBar } from '../../../../../components/layout/app-bar/base-app-bar'
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export default function AppBar() {
|
||||
useTranslation()
|
||||
return (
|
||||
<BaseAppBar>
|
||||
<Trans i18nKey={'explore.modes.public'} />
|
||||
</BaseAppBar>
|
||||
)
|
||||
}
|
18
frontend/src/app/(editor)/@appBar/explore/shared/page.tsx
Normal file
18
frontend/src/app/(editor)/@appBar/explore/shared/page.tsx
Normal file
|
@ -0,0 +1,18 @@
|
|||
'use client'
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { BaseAppBar } from '../../../../../components/layout/app-bar/base-app-bar'
|
||||
import React from 'react'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
export default function AppBar() {
|
||||
useTranslation()
|
||||
return (
|
||||
<BaseAppBar>
|
||||
<Trans i18nKey={'explore.modes.shared'} />
|
||||
</BaseAppBar>
|
||||
)
|
||||
}
|
31
frontend/src/app/(editor)/explore/layout.tsx
Normal file
31
frontend/src/app/(editor)/explore/layout.tsx
Normal file
|
@ -0,0 +1,31 @@
|
|||
'use client'
|
||||
|
||||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { PropsWithChildren } from 'react'
|
||||
import React from 'react'
|
||||
import { Container } from 'react-bootstrap'
|
||||
import { Welcome } from '../../../components/explore-page/welcome'
|
||||
import { ModeSelection } from '../../../components/explore-page/mode-selection/mode-selection'
|
||||
import { PinnedNotes } from '../../../components/explore-page/pinned-notes/pinned-notes'
|
||||
|
||||
/**
|
||||
* Layout for the login page with the intro content on the left and children on the right.
|
||||
* @param children The content to show on the right
|
||||
*/
|
||||
|
||||
export type ExploreLayoutProps = PropsWithChildren
|
||||
|
||||
export default function ExploreLayout({ children }: ExploreLayoutProps) {
|
||||
return (
|
||||
<Container>
|
||||
<Welcome />
|
||||
<PinnedNotes />
|
||||
<ModeSelection />
|
||||
{children}
|
||||
</Container>
|
||||
)
|
||||
}
|
14
frontend/src/app/(editor)/explore/my/page.tsx
Normal file
14
frontend/src/app/(editor)/explore/my/page.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { NextPage } from 'next'
|
||||
import { LandingLayout } from '../../../../components/landing-layout/landing-layout'
|
||||
|
||||
const ExploreMyNotesPage: NextPage = () => {
|
||||
return <LandingLayout>Own Notes</LandingLayout>
|
||||
}
|
||||
|
||||
export default ExploreMyNotesPage
|
14
frontend/src/app/(editor)/explore/public/page.tsx
Normal file
14
frontend/src/app/(editor)/explore/public/page.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { NextPage } from 'next'
|
||||
import { LandingLayout } from '../../../../components/landing-layout/landing-layout'
|
||||
|
||||
const ExplorePublicPage: NextPage = () => {
|
||||
return <LandingLayout>Public Notes</LandingLayout>
|
||||
}
|
||||
|
||||
export default ExplorePublicPage
|
14
frontend/src/app/(editor)/explore/shared/page.tsx
Normal file
14
frontend/src/app/(editor)/explore/shared/page.tsx
Normal file
|
@ -0,0 +1,14 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import type { NextPage } from 'next'
|
||||
import { LandingLayout } from '../../../../components/landing-layout/landing-layout'
|
||||
|
||||
const ExploreSharedPage: NextPage = () => {
|
||||
return <LandingLayout>Shared Notes</LandingLayout>
|
||||
}
|
||||
|
||||
export default ExploreSharedPage
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import type { NextPage } from 'next'
|
||||
import React from 'react'
|
||||
import { RedirectToParamOrHistory } from '../../../components/login-page/redirect-to-param-or-history'
|
||||
import { RedirectToParamOrExplore } from '../../../components/login-page/redirect-to-param-or-explore'
|
||||
import { LocalLoginCard } from '../../../components/login-page/local-login/local-login-card'
|
||||
import { LdapLoginCards } from '../../../components/login-page/ldap/ldap-login-cards'
|
||||
import { OneClickLoginCard } from '../../../components/login-page/one-click/one-click-login-card'
|
||||
|
@ -20,7 +20,7 @@ const LoginPage: NextPage = () => {
|
|||
const userLoggedIn = useIsLoggedIn()
|
||||
|
||||
if (userLoggedIn) {
|
||||
return <RedirectToParamOrHistory />
|
||||
return <RedirectToParamOrExplore />
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -8,7 +8,7 @@ import type { NextPage } from 'next'
|
|||
import { useTranslation } from 'react-i18next'
|
||||
import React from 'react'
|
||||
import { useIsLoggedIn } from '../../../hooks/common/use-is-logged-in'
|
||||
import { RedirectToParamOrHistory } from '../../../components/login-page/redirect-to-param-or-history'
|
||||
import { RedirectToParamOrExplore } from '../../../components/login-page/redirect-to-param-or-explore'
|
||||
import { NewUserCard } from '../../../components/login-page/new-user/new-user-card'
|
||||
import { LoginLayout } from '../../../components/layout/login-layout'
|
||||
|
||||
|
@ -20,7 +20,7 @@ const NewUserPage: NextPage = () => {
|
|||
const userLoggedIn = useIsLoggedIn()
|
||||
|
||||
if (userLoggedIn) {
|
||||
return <RedirectToParamOrHistory />
|
||||
return <RedirectToParamOrExplore />
|
||||
}
|
||||
|
||||
return (
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React, { useMemo } from 'react'
|
||||
import {
|
||||
FileEarmarkSlidesFill as IconFileEarmarkSlidesFill,
|
||||
FileEarmarkTextFill as IconFileEarmarkTextFill
|
||||
} from 'react-bootstrap-icons'
|
||||
import { NoteType } from '@hedgedoc/commons'
|
||||
import { UiIcon } from '../icons/ui-icon'
|
||||
import type { UiIconProps } from '../icons/ui-icon'
|
||||
|
||||
interface NoteTypeIconProps extends Omit<UiIconProps, 'icon'> {
|
||||
noteType: NoteType
|
||||
}
|
||||
|
||||
export const NoteTypeIcon: React.FC<NoteTypeIconProps> = ({ noteType, ...props }) => {
|
||||
const icon = useMemo(() => {
|
||||
switch (noteType) {
|
||||
case NoteType.DOCUMENT:
|
||||
default:
|
||||
return IconFileEarmarkTextFill
|
||||
case NoteType.SLIDE:
|
||||
return IconFileEarmarkSlidesFill
|
||||
}
|
||||
}, [noteType])
|
||||
|
||||
return <UiIcon icon={icon} {...props} />
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
.link {
|
||||
color: var(--bs-secondary-color);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.active {
|
||||
color: var(--bs-heading-color);
|
||||
text-decoration: none;
|
||||
font-weight: bold;
|
||||
cursor: text;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import { useMemo } from 'react'
|
||||
import React from 'react'
|
||||
import Link from 'next/link'
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import styles from './mode-link.module.css'
|
||||
import type { Mode } from './mode'
|
||||
|
||||
export interface ModeLinkProps {
|
||||
mode: Mode
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a link to switch to another mode of the explore page
|
||||
|
||||
* @param mode The target mode to link to
|
||||
*/
|
||||
export const ModeLink: React.FC<ModeLinkProps> = ({ mode }) => {
|
||||
useTranslation()
|
||||
const path = usePathname()
|
||||
const isActive = useMemo(() => path === `/explore/${mode}`, [path, mode])
|
||||
|
||||
return isActive ? (
|
||||
<span className={`${styles.link} ${styles.active}`}>
|
||||
<Trans i18nKey={`explore.modes.${mode}`} />
|
||||
</span>
|
||||
) : (
|
||||
<Link href={`/explore/${mode}`} className={styles.link}>
|
||||
<Trans i18nKey={`explore.modes.${mode}`} />
|
||||
</Link>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React from 'react'
|
||||
import { ModeLink } from './mode-link'
|
||||
import { useIsLoggedIn } from '../../../hooks/common/use-is-logged-in'
|
||||
import { Mode } from './mode'
|
||||
|
||||
/**
|
||||
* Renders the switcher for the different modes of the explore page.
|
||||
* Since notes can't be shared with anonymous guests, the shared mode is only shown to logged-in users.
|
||||
*/
|
||||
export const ModeSelection: React.FC = () => {
|
||||
const userLoggedIn = useIsLoggedIn()
|
||||
|
||||
return (
|
||||
<h2 className={'mb-3'}>
|
||||
<ModeLink mode={Mode.MY_NOTES} />
|
||||
{userLoggedIn && <ModeLink mode={Mode.SHARED_WITH_ME} />}
|
||||
<ModeLink mode={Mode.PUBLIC} />
|
||||
</h2>
|
||||
)
|
||||
}
|
10
frontend/src/components/explore-page/mode-selection/mode.ts
Normal file
10
frontend/src/components/explore-page/mode-selection/mode.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
export enum Mode {
|
||||
MY_NOTES = 'my',
|
||||
SHARED_WITH_ME = 'shared',
|
||||
PUBLIC = 'public'
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
.card {
|
||||
min-width: 25rem;
|
||||
height: 8rem;
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.card:hover {
|
||||
background-color: var(--bs-card-border-color);
|
||||
}
|
||||
|
||||
.bookmark {
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
right: 1rem;
|
||||
padding: 0;
|
||||
color: #c40c0c;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.star {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 1.5rem;
|
||||
padding: 0;
|
||||
width: 11px;
|
||||
height: 16px;
|
||||
background-color: #f8df33;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
width: 22rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
.titleText {
|
||||
padding-top: 2rem;
|
||||
padding-left: 0.5rem;
|
||||
}
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React, { type MouseEvent, useMemo } from 'react'
|
||||
import { Badge, Card } from 'react-bootstrap'
|
||||
import { DateTime } from 'luxon'
|
||||
import { BookmarkStarFill as IconPinned } from 'react-bootstrap-icons'
|
||||
import styles from './pinned-note-card.module.css'
|
||||
import { useCallback } from 'react'
|
||||
import { NoteTypeIcon } from '../../common/note-type-icon/note-type-icon'
|
||||
import type { NoteType } from '@hedgedoc/commons'
|
||||
import { UiIcon } from '../../common/icons/ui-icon'
|
||||
import Link from 'next/link'
|
||||
import { useTranslatedText } from '../../../hooks/common/use-translated-text'
|
||||
import { useRouter } from 'next/navigation'
|
||||
|
||||
export interface NoteCardProps {
|
||||
title: string
|
||||
id: string
|
||||
type: NoteType
|
||||
lastVisited: string
|
||||
created: string
|
||||
pinned: boolean
|
||||
tags: string[]
|
||||
primaryAddress: string
|
||||
}
|
||||
|
||||
export const PinnedNoteCard: React.FC<NoteCardProps> = ({ title, id, lastVisited, type, primaryAddress, tags }) => {
|
||||
const router = useRouter()
|
||||
const labelTag = useTranslatedText('explore.filters.byTag')
|
||||
const labelUnpinNote = useTranslatedText('explore.pinnedNotes.unpin')
|
||||
const lastVisitedString = useMemo(() => DateTime.fromISO(lastVisited).toRelative(), [lastVisited])
|
||||
// const createdString = DateTime.fromISO(created).toFormat('DDDD T')
|
||||
const onClickUnpin = useCallback(
|
||||
(event: MouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault()
|
||||
alert(`UnFav ${id}`)
|
||||
},
|
||||
[id]
|
||||
)
|
||||
|
||||
const onClickTag = useCallback(
|
||||
(tag: string) => {
|
||||
return (event: MouseEvent<HTMLDivElement>) => {
|
||||
event.preventDefault()
|
||||
router.push(`?search=tag:${tag}`)
|
||||
}
|
||||
},
|
||||
[router]
|
||||
)
|
||||
|
||||
const tagsChips = useMemo(() => {
|
||||
return tags.map((tag) => (
|
||||
<Badge key={tag} bg={'secondary'} pill={true} className={'me-1'} onClick={onClickTag(tag)} title={labelTag}>
|
||||
{tag}
|
||||
</Badge>
|
||||
))
|
||||
}, [tags, onClickTag, labelTag])
|
||||
|
||||
return (
|
||||
<li className={'d-block'}>
|
||||
<Card className={`${styles.card}`} as={Link} href={`/n/${primaryAddress}`}>
|
||||
<Card.Body>
|
||||
<div onClick={onClickUnpin} title={labelUnpinNote}>
|
||||
<UiIcon icon={IconPinned} size={1.5} className={`${styles.bookmark}`} />
|
||||
<div className={`${styles.star}`} />
|
||||
</div>
|
||||
<Card.Title className={`${styles.title}`}>
|
||||
<NoteTypeIcon noteType={type} />
|
||||
<span className={`${styles.titleText}`} title={title}>
|
||||
{title}
|
||||
</span>
|
||||
</Card.Title>
|
||||
<Card.Subtitle className='mb-2 text-muted'>{lastVisitedString}</Card.Subtitle>
|
||||
{tagsChips}
|
||||
</Card.Body>
|
||||
</Card>
|
||||
</li>
|
||||
)
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React, { Fragment, useMemo } 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'
|
||||
|
||||
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'
|
||||
}
|
||||
]
|
||||
|
||||
export const PinnedNotes: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
const cards = useMemo(() => {
|
||||
return mockListPinnedNotes.map((note: NoteCardProps) => <PinnedNoteCard key={note.id} {...note} />)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<h2 className={'mb-2'}>
|
||||
<Trans i18nKey={'explore.pinnedNotes.title'} />
|
||||
</h2>
|
||||
<ul className={'d-block mx-2 py-2 mb-5 d-flex gap-2 w-100 overflow-x-auto'}>{cards}</ul>
|
||||
</Fragment>
|
||||
)
|
||||
}
|
26
frontend/src/components/explore-page/welcome.tsx
Normal file
26
frontend/src/components/explore-page/welcome.tsx
Normal file
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import React from 'react'
|
||||
import { useApplicationState } from '../../hooks/common/use-application-state'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
|
||||
/**
|
||||
* Renders the welcome message for the explore page.
|
||||
*/
|
||||
export const Welcome: React.FC = () => {
|
||||
useTranslation()
|
||||
const userName = useApplicationState((state) => state.user?.displayName)
|
||||
|
||||
return (
|
||||
<h1 className={'my-4'}>
|
||||
{userName !== undefined ? (
|
||||
<Trans i18nKey={'explore.welcome.user'} values={{ userName }} />
|
||||
) : (
|
||||
<Trans i18nKey={'explore.welcome.guest'} />
|
||||
)}
|
||||
</h1>
|
||||
)
|
||||
}
|
|
@ -20,7 +20,7 @@ export const BrandingElement: React.FC = () => {
|
|||
|
||||
return (
|
||||
<Navbar.Brand>
|
||||
<Link href='/' className='text-secondary text-decoration-none d-flex align-items-center'>
|
||||
<Link href='/explore/my' className='text-secondary text-decoration-none d-flex align-items-center'>
|
||||
<HedgeDocLogoHorizontalGrey
|
||||
size={LogoSize.SMALL}
|
||||
className={'w-auto'}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
* SPDX-FileCopyrightText: 2025 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
@ -10,15 +10,15 @@ import { Trans, useTranslation } from 'react-i18next'
|
|||
import Link from 'next/link'
|
||||
|
||||
/**
|
||||
* A button that links to the history page.
|
||||
* A button that links to the public explore page.
|
||||
*/
|
||||
export const HistoryButton: React.FC = () => {
|
||||
export const ExploreButton: React.FC = () => {
|
||||
useTranslation()
|
||||
|
||||
return (
|
||||
<Link href={'/history'}>
|
||||
<Link href={'/explore/public'}>
|
||||
<Button variant={'secondary'} size={'sm'}>
|
||||
<Trans i18nKey='landing.navigation.history' />
|
||||
<Trans i18nKey='explore.modes.public' />
|
||||
</Button>
|
||||
</Link>
|
||||
)
|
|
@ -7,7 +7,7 @@
|
|||
import React from 'react'
|
||||
import { Card } from 'react-bootstrap'
|
||||
import { NewNoteButton } from '../../common/new-note-button/new-note-button'
|
||||
import { HistoryButton } from '../../layout/app-bar/app-bar-elements/help-dropdown/history-button'
|
||||
import { ExploreButton } from './explore-button'
|
||||
import { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { GuestAccessLevel } from '../../../api/config/types'
|
||||
|
@ -32,7 +32,7 @@ export const GuestCard: React.FC = () => {
|
|||
</Card.Title>
|
||||
<div className={'d-flex flex-row gap-2'}>
|
||||
<NewNoteButton />
|
||||
<HistoryButton />
|
||||
<ExploreButton />
|
||||
</div>
|
||||
{guestAccessLevel !== GuestAccessLevel.CREATE && (
|
||||
<div className={'text-muted mt-2 small'}>
|
||||
|
|
|
@ -12,7 +12,7 @@ import { useGetPostLoginRedirectUrl } from './utils/use-get-post-login-redirect-
|
|||
* Redirects the browser to the relative URL that is provided via "redirectBackTo" URL parameter.
|
||||
* If no parameter has been provided or if the URL is not relative, then "/history" will be used.
|
||||
*/
|
||||
export const RedirectToParamOrHistory: React.FC = () => {
|
||||
export const RedirectToParamOrExplore: React.FC = () => {
|
||||
const redirectUrl = useGetPostLoginRedirectUrl()
|
||||
return <Redirect to={redirectUrl} replace={true} />
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
*/
|
||||
import { useSingleStringUrlParameter } from '../../../hooks/common/use-single-string-url-parameter'
|
||||
|
||||
const defaultFallback = '/history'
|
||||
const defaultFallback = '/explore/my'
|
||||
|
||||
/**
|
||||
* Returns the URL that the user should be redirected to after logging in.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue