mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-12 13:13:45 +01:00
Merge pull request #36037 from overleaf/dp-library-trash-navigation-2
Add trash navigation with Projects/References tabs- #36001 GitOrigin-RevId: 675ad078f117c5acd8744024c751209c615a7c2e
This commit is contained in:
@@ -1647,6 +1647,7 @@
|
||||
"reference_sync": "",
|
||||
"references": "",
|
||||
"references_from_these_libraries_will_be_included_in_your_reference_search_results": "",
|
||||
"references_permanently_deleted_after_30_days": "",
|
||||
"refresh": "",
|
||||
"refresh_page_after_linking_dropbox": "",
|
||||
"refresh_page_after_starting_free_trial": "",
|
||||
@@ -1781,6 +1782,7 @@
|
||||
"search_in_library": "",
|
||||
"search_in_shared_projects": "",
|
||||
"search_in_trashed_projects": "",
|
||||
"search_in_trashed_references": "",
|
||||
"search_in_your_projects": "",
|
||||
"search_match_case": "",
|
||||
"search_members": "",
|
||||
@@ -2256,7 +2258,6 @@
|
||||
"trash_projects": "",
|
||||
"trashed": "",
|
||||
"trashed_projects": "",
|
||||
"trashed_references": "",
|
||||
"trashing_projects_wont_affect_collaborators": "",
|
||||
"trial_last_day": "",
|
||||
"trial_remaining_days": "",
|
||||
|
||||
+83
-10
@@ -59,14 +59,32 @@ export function Item({
|
||||
)
|
||||
}
|
||||
|
||||
function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
|
||||
function NavigationDropdown({
|
||||
activePage,
|
||||
trashActive = false,
|
||||
}: {
|
||||
activePage: ActivePage
|
||||
|
||||
/**
|
||||
* Whether the trash is active. This is only relevant when the active page is
|
||||
* "library" as which project page is active is determined by the selected
|
||||
* filter.
|
||||
*/
|
||||
trashActive?: boolean
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const [title, setTitle] = useState(() =>
|
||||
activePage === 'library' ? t('library') : t('all_projects')
|
||||
)
|
||||
const [view, setView] = useState<'top' | 'tags'>('top')
|
||||
const [view, setView] = useState<'top' | 'tags' | 'trash'>('top')
|
||||
const { filter, selectedTagId, tags } = useProjectListContext()
|
||||
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
|
||||
const projectsTrashActive =
|
||||
activePage === 'projects' &&
|
||||
selectedTagId === undefined &&
|
||||
filter === 'trashed'
|
||||
const referencesTrashActive = activePage === 'library' && trashActive
|
||||
const isTrashActive = projectsTrashActive || referencesTrashActive
|
||||
const filterTranslations = useRef<Record<Filter, string>>({
|
||||
all: t('all_projects'),
|
||||
owned: t('your_projects'),
|
||||
@@ -76,6 +94,11 @@ function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
|
||||
})
|
||||
|
||||
useEffect(() => {
|
||||
if (isTrashActive) {
|
||||
setTitle(t('trash'))
|
||||
return
|
||||
}
|
||||
|
||||
if (activePage === 'library') {
|
||||
setTitle(t('library'))
|
||||
return
|
||||
@@ -94,7 +117,7 @@ function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
|
||||
setTitle(tag.name ?? '')
|
||||
}
|
||||
}
|
||||
}, [filter, tags, selectedTagId, t, activePage])
|
||||
}, [filter, tags, selectedTagId, t, activePage, isTrashActive])
|
||||
|
||||
const filterItems = (
|
||||
<>
|
||||
@@ -158,7 +181,13 @@ function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
|
||||
isLibraryEnabled
|
||||
? show => {
|
||||
if (show) {
|
||||
setView(selectedTagId !== undefined ? 'tags' : 'top')
|
||||
if (selectedTagId !== undefined) {
|
||||
setView('tags')
|
||||
} else if (isTrashActive) {
|
||||
setView('trash')
|
||||
} else {
|
||||
setView('top')
|
||||
}
|
||||
} else {
|
||||
setView('top')
|
||||
}
|
||||
@@ -205,7 +234,7 @@ function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
|
||||
<OLDropdownDivider />
|
||||
<li role="none">
|
||||
<OLDropdownItem
|
||||
active={activePage === 'library'}
|
||||
active={activePage === 'library' && !trashActive}
|
||||
href="/library"
|
||||
leadingIcon={<BookBookmark size={20} />}
|
||||
>
|
||||
@@ -213,12 +242,18 @@ function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
|
||||
</OLDropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<Item
|
||||
filter="trashed"
|
||||
text={t('trash')}
|
||||
<OLDropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
trailingIcon="chevron_right"
|
||||
leadingIcon={<Trash size={20} />}
|
||||
activePage={activePage}
|
||||
/>
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
setView('trash')
|
||||
}}
|
||||
>
|
||||
{t('trash')}
|
||||
</OLDropdownItem>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
@@ -241,6 +276,44 @@ function NavigationDropdown({ activePage }: { activePage: ActivePage }) {
|
||||
<TagsList />
|
||||
</>
|
||||
)}
|
||||
{isLibraryEnabled && view === 'trash' && (
|
||||
<>
|
||||
<li role="none">
|
||||
<OLDropdownItem
|
||||
as="button"
|
||||
tabIndex={-1}
|
||||
leadingIcon={<MaterialIcon type="chevron_left" />}
|
||||
aria-label={t('back')}
|
||||
onClick={e => {
|
||||
e.stopPropagation()
|
||||
setView('top')
|
||||
}}
|
||||
>
|
||||
{t('trash')}
|
||||
</OLDropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<OLDropdownItem
|
||||
href="/project/trashed"
|
||||
active={projectsTrashActive}
|
||||
trailingIcon={projectsTrashActive ? 'check' : undefined}
|
||||
leadingIcon={<Folder size={20} />}
|
||||
>
|
||||
{t('projects')}
|
||||
</OLDropdownItem>
|
||||
</li>
|
||||
<li role="none">
|
||||
<OLDropdownItem
|
||||
href="/library/trashed"
|
||||
active={referencesTrashActive}
|
||||
trailingIcon={referencesTrashActive ? 'check' : undefined}
|
||||
leadingIcon={<BookBookmark size={20} />}
|
||||
>
|
||||
{t('references')}
|
||||
</OLDropdownItem>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</OLDropdownMenu>
|
||||
</OLDropdown>
|
||||
)
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import classNames from 'classnames'
|
||||
import { useProjectListContext } from '../context/project-list-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import CurrentPlanWidget from './current-plan-widget/current-plan-widget'
|
||||
@@ -9,6 +10,7 @@ import NavigationDropdown from './dropdown/navigation-dropdown'
|
||||
import SortByDropdown from './dropdown/sort-by-dropdown'
|
||||
import ProjectTools from './table/project-tools/project-tools'
|
||||
import ProjectListTitle from './title/project-list-title'
|
||||
import TrashPageTabs from './trash/trash-page-tabs'
|
||||
import LoadMore from './load-more'
|
||||
import OLCol from '@/shared/components/ol/ol-col'
|
||||
import OLRow from '@/shared/components/ol/ol-row'
|
||||
@@ -42,6 +44,7 @@ export function ProjectListDsNav() {
|
||||
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
|
||||
|
||||
const selectedTag = tags.find(tag => tag._id === selectedTagId)
|
||||
const showTrashHeader = isLibraryEnabled && filter === 'trashed'
|
||||
const showNewProjectButton =
|
||||
!isLibraryEnabled ||
|
||||
(filter !== 'shared' && filter !== 'archived' && filter !== 'trashed')
|
||||
@@ -84,7 +87,10 @@ export function ProjectListDsNav() {
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`project-ds-nav-page website-redesign${isLibraryEnabled ? ' library-enabled' : ''}`}
|
||||
className={classNames('project-ds-nav-page', 'website-redesign', {
|
||||
'library-enabled': isLibraryEnabled,
|
||||
'ds-nav-hides-top-navbar': showTrashHeader,
|
||||
})}
|
||||
>
|
||||
<SystemMessages />
|
||||
<DefaultNavbar
|
||||
@@ -103,25 +109,38 @@ export function ProjectListDsNav() {
|
||||
<UserNotifications />
|
||||
<main aria-labelledby="main-content">
|
||||
<div className="project-list-header-row">
|
||||
<ProjectListTitle
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
selectedTagId={selectedTagId}
|
||||
className="text-truncate d-none d-md-block"
|
||||
/>
|
||||
{showTrashHeader ? (
|
||||
<h1
|
||||
id="main-content"
|
||||
tabIndex={-1}
|
||||
className="project-list-title text-truncate d-none d-md-block mb-0"
|
||||
>
|
||||
{t('trash')}
|
||||
</h1>
|
||||
) : (
|
||||
<ProjectListTitle
|
||||
filter={filter}
|
||||
selectedTag={selectedTag}
|
||||
selectedTagId={selectedTagId}
|
||||
className="text-truncate d-none d-md-block"
|
||||
/>
|
||||
)}
|
||||
<div className="project-tools">
|
||||
<div className="d-none d-md-block">
|
||||
{selectedProjects.length === 0 ? (
|
||||
<CurrentPlanWidget />
|
||||
showTrashHeader ? null : (
|
||||
<CurrentPlanWidget />
|
||||
)
|
||||
) : (
|
||||
<ProjectTools />
|
||||
)}
|
||||
</div>
|
||||
<div className="d-md-none">
|
||||
<CurrentPlanWidget />
|
||||
{!showTrashHeader && <CurrentPlanWidget />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{showTrashHeader && <TrashPageTabs activeTab="projects" />}
|
||||
<div className="project-ds-nav-project-list">
|
||||
<OLRow className="d-none d-md-flex align-items-center">
|
||||
<OLCol md={isLibraryEnabled ? 8 : undefined} lg={7}>
|
||||
|
||||
+3
-1
@@ -52,7 +52,9 @@ function SidebarDsNav({
|
||||
},
|
||||
})}
|
||||
>
|
||||
{isLibraryEnabled && activePage === 'library' && <DsNavOverleafLogo />}
|
||||
{isLibraryEnabled && (activePage === 'library' || isTrashActive) && (
|
||||
<DsNavOverleafLogo />
|
||||
)}
|
||||
<nav
|
||||
className="flex-grow flex-shrink"
|
||||
aria-label={t('project_categories_tags')}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import classnames from 'classnames'
|
||||
|
||||
export type TrashTab = 'projects' | 'references'
|
||||
|
||||
function TrashPageTabs({ activeTab }: { activeTab: TrashTab }) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const tabs: { key: TrashTab; label: string; href: string }[] = [
|
||||
{ key: 'projects', label: t('projects'), href: '/project/trashed' },
|
||||
{ key: 'references', label: t('references'), href: '/library/trashed' },
|
||||
]
|
||||
|
||||
return (
|
||||
<nav className="trash-page-tabs" aria-label={t('trash')}>
|
||||
{tabs.map(tab => {
|
||||
const active = tab.key === activeTab
|
||||
return (
|
||||
<a
|
||||
key={tab.key}
|
||||
href={tab.href}
|
||||
className={classnames('trash-page-tab', { active })}
|
||||
aria-current={active ? 'page' : undefined}
|
||||
>
|
||||
{tab.label}
|
||||
</a>
|
||||
)
|
||||
})}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
|
||||
export default TrashPageTabs
|
||||
@@ -147,7 +147,7 @@ body {
|
||||
}
|
||||
}
|
||||
|
||||
&.library-page {
|
||||
&.ds-nav-hides-top-navbar {
|
||||
// The sidebar shows the logo and account/help menus from md upwards, so
|
||||
// the top navbar is only needed on mobile.
|
||||
.navbar-default {
|
||||
@@ -164,6 +164,19 @@ body {
|
||||
border-top: none;
|
||||
}
|
||||
}
|
||||
|
||||
.project-list-header-row {
|
||||
@include media-breakpoint-up(md) {
|
||||
margin-top: var(--spacing-08);
|
||||
}
|
||||
|
||||
// On mobile the trash page has no heading or plan widget in this row
|
||||
// (both are desktop-only), so collapse it to avoid an empty gap above
|
||||
// the navigation dropdown.
|
||||
@include media-breakpoint-down(md) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.navbar-nav > li > .dropdown-toggle::after {
|
||||
@@ -559,3 +572,36 @@ body {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.trash-page-tabs {
|
||||
display: flex;
|
||||
gap: var(--spacing-06);
|
||||
margin-bottom: var(--spacing-06);
|
||||
border-bottom: 1px solid var(--border-divider-themed);
|
||||
|
||||
// On mobile the Projects/References switch lives in the navigation dropdown
|
||||
// submenu instead of these tabs.
|
||||
@include media-breakpoint-down(md) {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.trash-page-tab {
|
||||
padding: var(--spacing-03) var(--spacing-02);
|
||||
margin-bottom: -1px;
|
||||
border-bottom: 2px solid transparent;
|
||||
color: var(--content-secondary-themed);
|
||||
font-size: var(--font-size-03);
|
||||
text-decoration: none;
|
||||
|
||||
&:hover {
|
||||
color: var(--content-primary-themed);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
&.active {
|
||||
border-bottom-color: var(--green-50);
|
||||
color: var(--content-primary-themed);
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@
|
||||
min-height: 36px;
|
||||
|
||||
.project-list-title {
|
||||
@include heading-sm;
|
||||
@include heading-md;
|
||||
|
||||
color: var(--content-secondary-themed);
|
||||
font-weight: bold;
|
||||
|
||||
@@ -2143,6 +2143,7 @@
|
||||
"reference_sync": "Reference manager sync",
|
||||
"references": "References",
|
||||
"references_from_these_libraries_will_be_included_in_your_reference_search_results": "References from these libraries will be included in your reference search results.",
|
||||
"references_permanently_deleted_after_30_days": "References are permanently deleted after 30 days.",
|
||||
"refresh": "Refresh",
|
||||
"refresh_page_after_linking_dropbox": "Please refresh this page after linking your account to Dropbox.",
|
||||
"refresh_page_after_starting_free_trial": "Please refresh this page after starting your free trial.",
|
||||
@@ -2315,6 +2316,7 @@
|
||||
"search_in_library": "Search in your library",
|
||||
"search_in_shared_projects": "Search in projects shared with you",
|
||||
"search_in_trashed_projects": "Search in trashed projects",
|
||||
"search_in_trashed_references": "Search in trashed references",
|
||||
"search_in_your_projects": "Search in your projects",
|
||||
"search_match_case": "Match case",
|
||||
"search_members": "Search members",
|
||||
@@ -2875,7 +2877,6 @@
|
||||
"trash_projects": "Trash Projects",
|
||||
"trashed": "Trashed",
|
||||
"trashed_projects": "Trashed projects",
|
||||
"trashed_references": "Trashed references",
|
||||
"trashing_projects_wont_affect_collaborators": "Trashing projects won’t affect your collaborators.",
|
||||
"trial_last_day": "This is the last day of your <b>Overleaf Premium</b> trial",
|
||||
"trial_remaining_days": "__days__ more days on your <b>Overleaf Premium</b> trial",
|
||||
|
||||
Reference in New Issue
Block a user