mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-06-03 16:38:50 -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",
|
"slogan": "Ideas grow better together",
|
||||||
"icon": "HedgeDoc logo with text"
|
"icon": "HedgeDoc logo with text"
|
||||||
},
|
},
|
||||||
"notificationTest": {
|
|
||||||
"title": "Test",
|
|
||||||
"content": "It works!"
|
|
||||||
},
|
|
||||||
"navigation": {
|
"navigation": {
|
||||||
"newNote": "New Note"
|
"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": {
|
"renderer": {
|
||||||
"highlightCode": {
|
"highlightCode": {
|
||||||
"copyCode": "Copy code to clipboard"
|
"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 type { NextPage } from 'next'
|
||||||
import React from 'react'
|
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 { LocalLoginCard } from '../../../components/login-page/local-login/local-login-card'
|
||||||
import { LdapLoginCards } from '../../../components/login-page/ldap/ldap-login-cards'
|
import { LdapLoginCards } from '../../../components/login-page/ldap/ldap-login-cards'
|
||||||
import { OneClickLoginCard } from '../../../components/login-page/one-click/one-click-login-card'
|
import { OneClickLoginCard } from '../../../components/login-page/one-click/one-click-login-card'
|
||||||
|
@ -20,7 +20,7 @@ const LoginPage: NextPage = () => {
|
||||||
const userLoggedIn = useIsLoggedIn()
|
const userLoggedIn = useIsLoggedIn()
|
||||||
|
|
||||||
if (userLoggedIn) {
|
if (userLoggedIn) {
|
||||||
return <RedirectToParamOrHistory />
|
return <RedirectToParamOrExplore />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
|
@ -8,7 +8,7 @@ import type { NextPage } from 'next'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { useIsLoggedIn } from '../../../hooks/common/use-is-logged-in'
|
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 { NewUserCard } from '../../../components/login-page/new-user/new-user-card'
|
||||||
import { LoginLayout } from '../../../components/layout/login-layout'
|
import { LoginLayout } from '../../../components/layout/login-layout'
|
||||||
|
|
||||||
|
@ -20,7 +20,7 @@ const NewUserPage: NextPage = () => {
|
||||||
const userLoggedIn = useIsLoggedIn()
|
const userLoggedIn = useIsLoggedIn()
|
||||||
|
|
||||||
if (userLoggedIn) {
|
if (userLoggedIn) {
|
||||||
return <RedirectToParamOrHistory />
|
return <RedirectToParamOrExplore />
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
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 (
|
return (
|
||||||
<Navbar.Brand>
|
<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
|
<HedgeDocLogoHorizontalGrey
|
||||||
size={LogoSize.SMALL}
|
size={LogoSize.SMALL}
|
||||||
className={'w-auto'}
|
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
|
* SPDX-License-Identifier: AGPL-3.0-only
|
||||||
*/
|
*/
|
||||||
|
@ -10,15 +10,15 @@ import { Trans, useTranslation } from 'react-i18next'
|
||||||
import Link from 'next/link'
|
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()
|
useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={'/history'}>
|
<Link href={'/explore/public'}>
|
||||||
<Button variant={'secondary'} size={'sm'}>
|
<Button variant={'secondary'} size={'sm'}>
|
||||||
<Trans i18nKey='landing.navigation.history' />
|
<Trans i18nKey='explore.modes.public' />
|
||||||
</Button>
|
</Button>
|
||||||
</Link>
|
</Link>
|
||||||
)
|
)
|
|
@ -7,7 +7,7 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
import { Card } from 'react-bootstrap'
|
import { Card } from 'react-bootstrap'
|
||||||
import { NewNoteButton } from '../../common/new-note-button/new-note-button'
|
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 { useFrontendConfig } from '../../common/frontend-config-context/use-frontend-config'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import { GuestAccessLevel } from '../../../api/config/types'
|
import { GuestAccessLevel } from '../../../api/config/types'
|
||||||
|
@ -32,7 +32,7 @@ export const GuestCard: React.FC = () => {
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
<div className={'d-flex flex-row gap-2'}>
|
<div className={'d-flex flex-row gap-2'}>
|
||||||
<NewNoteButton />
|
<NewNoteButton />
|
||||||
<HistoryButton />
|
<ExploreButton />
|
||||||
</div>
|
</div>
|
||||||
{guestAccessLevel !== GuestAccessLevel.CREATE && (
|
{guestAccessLevel !== GuestAccessLevel.CREATE && (
|
||||||
<div className={'text-muted mt-2 small'}>
|
<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.
|
* 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.
|
* 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()
|
const redirectUrl = useGetPostLoginRedirectUrl()
|
||||||
return <Redirect to={redirectUrl} replace={true} />
|
return <Redirect to={redirectUrl} replace={true} />
|
||||||
}
|
}
|
|
@ -5,7 +5,7 @@
|
||||||
*/
|
*/
|
||||||
import { useSingleStringUrlParameter } from '../../../hooks/common/use-single-string-url-parameter'
|
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.
|
* Returns the URL that the user should be redirected to after logging in.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue