Merge pull request #36649 from overleaf/dp-cleanup-library-feature-flag

Remove the overleaf-library feature flag

GitOrigin-RevId: ebeabd43c9e44dbc2344b4e8e7e5b9095fcc86e0
This commit is contained in:
David
2026-09-17 12:04:13 +00:00
committed by Copybot
parent 6c802c03a6
commit 90be47c5e1
18 changed files with 148 additions and 237 deletions
@@ -639,7 +639,6 @@ const _ProjectController = {
'export-markdown', 'export-markdown',
'export-html', 'export-html',
'command-palette', 'command-palette',
'overleaf-library',
'focus-mode', 'focus-mode',
'markdown-visual', 'markdown-visual',
'ai-disabled-collaborators', 'ai-disabled-collaborators',
@@ -574,7 +574,6 @@ async function projectListPage(req, res, next) {
const splitTests = [ const splitTests = [
// Split tests that will be made available to the frontend // Split tests that will be made available to the frontend
'import-docx', 'import-docx',
'overleaf-library',
'import-markdown', 'import-markdown',
'themed-modals', 'themed-modals',
'shared-workspace', 'shared-workspace',
+1
View File
@@ -1031,6 +1031,7 @@ module.exports = {
createFileModes: [], createFileModes: [],
devToolbar: [], devToolbar: [],
dsNavLibraryLink: [], dsNavLibraryLink: [],
trashPageTabs: [],
adminUserLibrary: [], adminUserLibrary: [],
gitBridge: [], gitBridge: [],
publishModalDropdownButton: [], publishModalDropdownButton: [],
@@ -741,7 +741,6 @@
"file_tree": "", "file_tree": "",
"files_cannot_include_invalid_characters": "", "files_cannot_include_invalid_characters": "",
"files_selected": "", "files_selected": "",
"filter_projects": "",
"find": "", "find": "",
"find_out_how_to_get_the_most_out_of_your_new_subscription": "", "find_out_how_to_get_the_most_out_of_your_new_subscription": "",
"find_out_more": "", "find_out_more": "",
@@ -1846,7 +1845,6 @@
"search_match_case": "", "search_match_case": "",
"search_members": "", "search_members": "",
"search_next": "", "search_next": "",
"search_only_the_bib_files_in_your_project_only_by_citekeys": "",
"search_previous": "", "search_previous": "",
"search_project": "", "search_project": "",
"search_projects": "", "search_projects": "",
@@ -4,12 +4,10 @@ import OLTooltip from '@/shared/components/ol/ol-tooltip'
import MaterialIcon from '@/shared/components/material-icon' import MaterialIcon from '@/shared/components/material-icon'
import { FreePlanSubscription } from '../../../../../../types/project/dashboard/subscription' import { FreePlanSubscription } from '../../../../../../types/project/dashboard/subscription'
import * as eventTracking from '../../../../infrastructure/event-tracking' import * as eventTracking from '../../../../infrastructure/event-tracking'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
type FreePlanProps = Pick<FreePlanSubscription, 'featuresPageURL'> type FreePlanProps = Pick<FreePlanSubscription, 'featuresPageURL'>
function FreePlan({ featuresPageURL }: FreePlanProps) { function FreePlan({ featuresPageURL }: FreePlanProps) {
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
const { t } = useTranslation() const { t } = useTranslation()
const currentPlanLabel = ( const currentPlanLabel = (
<Trans <Trans
@@ -45,8 +43,8 @@ function FreePlan({ featuresPageURL }: FreePlanProps) {
</OLTooltip>{' '} </OLTooltip>{' '}
<span className="d-none d-md-inline-block"> <span className="d-none d-md-inline-block">
<OLButton <OLButton
variant={isLibraryEnabled ? 'premium' : 'primary'} variant="premium"
size={isLibraryEnabled ? 'sm' : undefined} size="sm"
href="/user/subscription/plans" href="/user/subscription/plans"
onClick={handleClick} onClick={handleClick}
> >
@@ -9,13 +9,11 @@ import {
import { import {
OLDropdown, OLDropdown,
OLDropdownDivider, OLDropdownDivider,
OLDropdownHeader,
OLDropdownItem, OLDropdownItem,
OLDropdownMenu, OLDropdownMenu,
OLDropdownToggle, OLDropdownToggle,
} from '@/shared/components/ol/ol-dropdown-menu' } from '@/shared/components/ol/ol-dropdown-menu'
import MaterialIcon from '@/shared/components/material-icon' import MaterialIcon from '@/shared/components/material-icon'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import ProjectsFilterMenu from '../projects-filter-menu' import ProjectsFilterMenu from '../projects-filter-menu'
import TagsList from '../tags-list' import TagsList from '../tags-list'
import { ActivePage } from '../../util/navigation-state' import { ActivePage } from '../../util/navigation-state'
@@ -78,7 +76,6 @@ function NavigationDropdown({
) )
const [view, setView] = useState<'top' | 'tags' | 'trash'>('top') const [view, setView] = useState<'top' | 'tags' | 'trash'>('top')
const { filter, selectedTagId, tags } = useProjectListContext() const { filter, selectedTagId, tags } = useProjectListContext()
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
const projectsTrashActive = const projectsTrashActive =
activePage === 'projects' && activePage === 'projects' &&
selectedTagId === undefined && selectedTagId === undefined &&
@@ -126,7 +123,7 @@ function NavigationDropdown({
filter="all" filter="all"
text={t('all_projects')} text={t('all_projects')}
activePage={activePage} activePage={activePage}
leadingIcon={isLibraryEnabled && <Folder size={20} />} leadingIcon={<Folder size={20} />}
/> />
</li> </li>
<li role="none"> <li role="none">
@@ -134,7 +131,7 @@ function NavigationDropdown({
filter="owned" filter="owned"
text={t('your_projects')} text={t('your_projects')}
activePage={activePage} activePage={activePage}
leadingIcon={isLibraryEnabled && <OLDropdownItem.EmptyLeadingIcon />} leadingIcon={<OLDropdownItem.EmptyLeadingIcon />}
/> />
</li> </li>
<li role="none"> <li role="none">
@@ -142,7 +139,7 @@ function NavigationDropdown({
filter="shared" filter="shared"
text={t('shared_with_you')} text={t('shared_with_you')}
activePage={activePage} activePage={activePage}
leadingIcon={isLibraryEnabled && <OLDropdownItem.EmptyLeadingIcon />} leadingIcon={<OLDropdownItem.EmptyLeadingIcon />}
/> />
</li> </li>
<li role="none"> <li role="none">
@@ -150,71 +147,40 @@ function NavigationDropdown({
filter="archived" filter="archived"
text={t('archived_projects')} text={t('archived_projects')}
activePage={activePage} activePage={activePage}
leadingIcon={isLibraryEnabled && <OLDropdownItem.EmptyLeadingIcon />} leadingIcon={<OLDropdownItem.EmptyLeadingIcon />}
/> />
</li> </li>
{!isLibraryEnabled && (
<li role="none">
<Item
filter="trashed"
text={t('trashed_projects')}
activePage={activePage}
/>
</li>
)}
</>
)
const submenuItems = (
<>
{filterItems}
<OLDropdownHeader className="text-uppercase">
{t('tags')}:
</OLDropdownHeader>
<TagsList />
</> </>
) )
return ( return (
<OLDropdown <OLDropdown
onToggle={ onToggle={show => {
isLibraryEnabled if (show) {
? show => { if (selectedTagId !== undefined) {
if (show) { setView('tags')
if (selectedTagId !== undefined) { } else if (isTrashActive) {
setView('tags') setView('trash')
} else if (isTrashActive) { } else {
setView('trash') setView('top')
} else { }
setView('top') } else {
} setView('top')
} else { }
setView('top') }}
}
}
: undefined
}
> >
<OLDropdownToggle <OLDropdownToggle
id="projects-types-dropdown-toggle-btn" id="projects-types-dropdown-toggle-btn"
className="ps-0 mb-0 btn-transparent h3" className="ps-0 mb-0 btn-transparent h3"
size="lg" size="lg"
aria-label={ aria-label={t('navigation_menu')}
isLibraryEnabled ? t('navigation_menu') : t('filter_projects')
}
> >
<span className="text-truncate" aria-hidden> <span className="text-truncate" aria-hidden>
{title} {title}
</span> </span>
</OLDropdownToggle> </OLDropdownToggle>
<OLDropdownMenu <OLDropdownMenu flip={false} className="projects-dropdown-menu-library">
flip={false} {view === 'top' && (
className={
isLibraryEnabled ? 'projects-dropdown-menu-library' : undefined
}
>
{!isLibraryEnabled && submenuItems}
{isLibraryEnabled && view === 'top' && (
<> <>
{filterItems} {filterItems}
<li role="none"> <li role="none">
@@ -257,7 +223,7 @@ function NavigationDropdown({
</li> </li>
</> </>
)} )}
{isLibraryEnabled && view === 'tags' && ( {view === 'tags' && (
<> <>
<li role="none"> <li role="none">
<OLDropdownItem <OLDropdownItem
@@ -276,7 +242,7 @@ function NavigationDropdown({
<TagsList /> <TagsList />
</> </>
)} )}
{isLibraryEnabled && view === 'trash' && ( {view === 'trash' && (
<> <>
<li role="none"> <li role="none">
<OLDropdownItem <OLDropdownItem
@@ -22,7 +22,6 @@ import { useSendProjectListMB } from '@/features/project-list/components/project
import type { PortalTemplate } from '../../../../../types/portal-template' import type { PortalTemplate } from '../../../../../types/portal-template'
import { useFeatureFlag } from '@/shared/context/split-test-context' import { useFeatureFlag } from '@/shared/context/split-test-context'
import { useProjectListContext } from '@/features/project-list/context/project-list-context' import { useProjectListContext } from '@/features/project-list/context/project-list-context'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { NestableDropdownContextProvider } from '@/shared/context/nestable-dropdown-context' import { NestableDropdownContextProvider } from '@/shared/context/nestable-dropdown-context'
import { NestedMenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown' import { NestedMenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
@@ -72,11 +71,9 @@ function NewProjectButton({
useFeatureFlag('import-markdown') && useFeatureFlag('import-markdown') &&
getMeta('ol-ExposedSettings').enablePandocConversions getMeta('ol-ExposedSettings').enablePandocConversions
const { selectedTagId, tags } = useProjectListContext() const { selectedTagId, tags } = useProjectListContext()
const isLibraryEnabled = isSplitTestEnabled('overleaf-library') const initialTags = selectedTagId
const initialTags = ? tags.filter(tag => tag._id === selectedTagId)
isLibraryEnabled && selectedTagId : []
? tags.filter(tag => tag._id === selectedTagId)
: []
const sendTrackingEvent = useCallback( const sendTrackingEvent = useCallback(
({ ({
dropdownMenu, dropdownMenu,
@@ -26,7 +26,6 @@ import overleafLogoDark from '@/shared/svgs/overleaf-a-ds-solution-mallard-dark.
import CookieBanner from '@/shared/components/cookie-banner' import CookieBanner from '@/shared/components/cookie-banner'
import NoProjects from '@/features/project-list/components/no-projects' import NoProjects from '@/features/project-list/components/no-projects'
import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme' import { useActiveOverallTheme } from '@/shared/hooks/use-active-overall-theme'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { useFeatureFlag } from '@/shared/context/split-test-context' import { useFeatureFlag } from '@/shared/context/split-test-context'
export function ProjectListDsNav() { export function ProjectListDsNav() {
@@ -44,48 +43,28 @@ export function ProjectListDsNav() {
currentFilterProjectsCount, currentFilterProjectsCount,
} = useProjectListContext() } = useProjectListContext()
const activeOverallTheme = useActiveOverallTheme() const activeOverallTheme = useActiveOverallTheme()
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
const isSharedWorkspaceEnabled = useFeatureFlag('shared-workspace') const isSharedWorkspaceEnabled = useFeatureFlag('shared-workspace')
const selectedTag = tags.find(tag => tag._id === selectedTagId) const selectedTag = tags.find(tag => tag._id === selectedTagId)
const showTrashHeader = isLibraryEnabled && filter === 'trashed' const showTrashHeader = filter === 'trashed'
const showNewProjectButton = const showNewProjectButton =
!isLibraryEnabled || filter !== 'shared' && filter !== 'archived' && filter !== 'trashed'
(filter !== 'shared' && filter !== 'archived' && filter !== 'trashed')
const tableTopArea = ( const tableTopArea = (
<div className="pt-2 pb-3 d-md-none d-flex gap-2"> <div className="pt-2 pb-3 d-md-none d-flex gap-2">
{isLibraryEnabled ? ( <SearchForm
<> inputValue={searchText}
<SearchForm setInputValue={setSearchText}
inputValue={searchText} filter={filter}
setInputValue={setSearchText} selectedTag={selectedTag}
filter={filter} className="overflow-hidden flex-grow-1"
selectedTag={selectedTag} />
className="overflow-hidden flex-grow-1" {showNewProjectButton && (
/> <NewProjectButton
{showNewProjectButton && ( id="new-project-button-projects-table"
<NewProjectButton showAddAffiliationWidget
id="new-project-button-projects-table" align="end"
showAddAffiliationWidget />
align="end"
/>
)}
</>
) : (
<>
<NewProjectButton
id="new-project-button-projects-table"
showAddAffiliationWidget
/>
<SearchForm
inputValue={searchText}
setInputValue={setSearchText}
filter={filter}
selectedTag={selectedTag}
className="overflow-hidden flex-grow-1"
/>
</>
)} )}
</div> </div>
) )
@@ -93,7 +72,6 @@ export function ProjectListDsNav() {
return ( return (
<div <div
className={classNames('project-ds-nav-page', 'website-redesign', { className={classNames('project-ds-nav-page', 'website-redesign', {
'library-enabled': isLibraryEnabled,
'ds-nav-hides-top-navbar': showTrashHeader, 'ds-nav-hides-top-navbar': showTrashHeader,
})} })}
> >
@@ -159,7 +137,7 @@ export function ProjectListDsNav() {
) : ( ) : (
<div className="project-ds-nav-project-list"> <div className="project-ds-nav-project-list">
<OLRow className="d-none d-md-flex align-items-center"> <OLRow className="d-none d-md-flex align-items-center">
<OLCol md={isLibraryEnabled ? 8 : undefined} lg={7}> <OLCol md={8} lg={7}>
<SearchForm <SearchForm
inputValue={searchText} inputValue={searchText}
setInputValue={setSearchText} setInputValue={setSearchText}
@@ -167,7 +145,7 @@ export function ProjectListDsNav() {
selectedTag={selectedTag} selectedTag={selectedTag}
/> />
</OLCol> </OLCol>
{isLibraryEnabled && showNewProjectButton && ( {showNewProjectButton && (
<OLCol className="ms-auto" xs="auto"> <OLCol className="ms-auto" xs="auto">
<NewProjectButton <NewProjectButton
id="new-project-button-projects-table" id="new-project-button-projects-table"
@@ -2,14 +2,12 @@ import { JSXElementConstructor } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import classnames from 'classnames' import classnames from 'classnames'
import { Trash } from '@phosphor-icons/react' import { Trash } from '@phosphor-icons/react'
import NewProjectButton from '../new-project-button'
import SidebarFilters from './sidebar-filters' import SidebarFilters from './sidebar-filters'
import AddAffiliation, { useAddAffiliation } from '../add-affiliation' import AddAffiliation, { useAddAffiliation } from '../add-affiliation'
import { usePersistedResize } from '@/shared/hooks/use-resize' import { usePersistedResize } from '@/shared/hooks/use-resize'
import { useScrolled } from '@/features/project-list/components/sidebar/use-scroll' import { useScrolled } from '@/features/project-list/components/sidebar/use-scroll'
import { SurveyWidgetDsNav } from '@/features/project-list/components/survey-widget-ds-nav' import { SurveyWidgetDsNav } from '@/features/project-list/components/survey-widget-ds-nav'
import { SidebarLowerSection } from '@/shared/components/sidebar/sidebar-lower-section' import { SidebarLowerSection } from '@/shared/components/sidebar/sidebar-lower-section'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { DsNavOverleafLogo } from '@/shared/components/sidebar/ds-nav-overleaf-logo' import { DsNavOverleafLogo } from '@/shared/components/sidebar/ds-nav-overleaf-logo'
import { useProjectListContext } from '@/features/project-list/context/project-list-context' import { useProjectListContext } from '@/features/project-list/context/project-list-context'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
@@ -29,7 +27,6 @@ function SidebarDsNav({
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const { show: showAddAffiliationWidget } = useAddAffiliation() const { show: showAddAffiliationWidget } = useAddAffiliation()
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
const { filter, selectedTagId, selectFilter } = useProjectListContext() const { filter, selectedTagId, selectFilter } = useProjectListContext()
const isTrashActive = const isTrashActive =
activePage === 'library' activePage === 'library'
@@ -43,7 +40,7 @@ function SidebarDsNav({
const { mousePos, getHandleProps, getTargetProps } = usePersistedResize({ const { mousePos, getHandleProps, getTargetProps } = usePersistedResize({
name: 'project-sidebar', name: 'project-sidebar',
}) })
const { containerRef, scrolledUp, scrolledDown } = useScrolled() const { containerRef, scrolledUp } = useScrolled()
return ( return (
<div <div
@@ -54,19 +51,11 @@ function SidebarDsNav({
}, },
})} })}
> >
{isLibraryEnabled && (activePage === 'library' || isTrashActive) && ( {(activePage === 'library' || isTrashActive) && <DsNavOverleafLogo />}
<DsNavOverleafLogo />
)}
<nav <nav
className="flex-grow flex-shrink" className="flex-grow flex-shrink"
aria-label={t('project_categories_tags')} aria-label={t('project_categories_tags')}
> >
{!isLibraryEnabled && (
<NewProjectButton
id="new-project-button-sidebar"
className={scrolledDown ? 'show-shadow' : undefined}
/>
)}
<div <div
className="project-list-sidebar-scroll" className="project-list-sidebar-scroll"
ref={containerRef} ref={containerRef}
@@ -84,29 +73,25 @@ function SidebarDsNav({
)} )}
> >
<SidebarLowerSection showThemeToggle> <SidebarLowerSection showThemeToggle>
{isLibraryEnabled && ( {DsNavLibraryLink && (
<> <DsNavLibraryLink
{DsNavLibraryLink && ( active={activePage === 'library' && !trashActive}
<DsNavLibraryLink inLibrary={activePage === 'library'}
active={activePage === 'library' && !trashActive} />
inLibrary={activePage === 'library'}
/>
)}
<button
type="button"
className={classnames('ds-nav-page-switcher-item', {
active: isTrashActive,
})}
aria-current={isTrashActive ? 'page' : undefined}
onClick={() => selectFilter('trashed')}
>
<Trash size={20} />
<span className="ds-nav-page-switcher-item-label">
{t('trash')}
</span>
</button>
</>
)} )}
<button
type="button"
className={classnames('ds-nav-page-switcher-item', {
active: isTrashActive,
})}
aria-current={isTrashActive ? 'page' : undefined}
onClick={() => selectFilter('trashed')}
>
<Trash size={20} />
<span className="ds-nav-page-switcher-item-label">
{t('trash')}
</span>
</button>
<div className="project-list-sidebar-survey-wrapper"> <div className="project-list-sidebar-survey-wrapper">
<SurveyWidgetDsNav /> <SurveyWidgetDsNav />
</div> </div>
@@ -6,7 +6,6 @@ import {
} from '../../context/project-list-context' } from '../../context/project-list-context'
import TagsList from './tags-list' import TagsList from './tags-list'
import ProjectsFilterMenu from '../projects-filter-menu' import ProjectsFilterMenu from '../projects-filter-menu'
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
import { Folder } from '@phosphor-icons/react' import { Folder } from '@phosphor-icons/react'
import { ActivePage } from '../../util/navigation-state' import { ActivePage } from '../../util/navigation-state'
@@ -51,14 +50,13 @@ export default function SidebarFilters({
activePage: ActivePage activePage: ActivePage
}) { }) {
const { t } = useTranslation() const { t } = useTranslation()
const isLibraryEnabled = isSplitTestEnabled('overleaf-library')
return ( return (
<ul className="list-unstyled project-list-filters"> <ul className="list-unstyled project-list-filters">
<SidebarFilter <SidebarFilter
activePage={activePage} activePage={activePage}
filter="all" filter="all"
text={isLibraryEnabled ? t('projects') : t('all_projects')} text={t('projects')}
icon={isLibraryEnabled && <Folder size={20} />} icon={<Folder size={20} />}
/> />
<SidebarFilter <SidebarFilter
activePage={activePage} activePage={activePage}
@@ -75,13 +73,6 @@ export default function SidebarFilters({
filter="archived" filter="archived"
text={t('archived_projects')} text={t('archived_projects')}
/> />
{!isLibraryEnabled && (
<SidebarFilter
activePage={activePage}
filter="trashed"
text={t('trashed_projects')}
/>
)}
<li aria-hidden="true"> <li aria-hidden="true">
<hr /> <hr />
</li> </li>
@@ -1,16 +1,32 @@
import { TFunction } from 'i18next'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import classnames from 'classnames' import classnames from 'classnames'
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
export type TrashTab = 'projects' | 'references' export type TrashTab = 'projects' | 'references'
export type TrashPageTab = { key: TrashTab; label: string; href: string }
type TrashPageTabModule = {
import: { default: (t: TFunction) => TrashPageTab }
path: string
}
const trashPageTabModules: TrashPageTabModule[] =
importOverleafModules('trashPageTabs')
function TrashPageTabs({ activeTab }: { activeTab: TrashTab }) { function TrashPageTabs({ activeTab }: { activeTab: TrashTab }) {
const { t } = useTranslation() const { t } = useTranslation()
const tabs: { key: TrashTab; label: string; href: string }[] = [ const tabs: TrashPageTab[] = [
{ key: 'projects', label: t('projects'), href: '/project/trashed' }, { key: 'projects', label: t('projects'), href: '/project/trashed' },
{ key: 'references', label: t('references'), href: '/library/trashed' }, ...trashPageTabModules.map(({ import: getTab }) => getTab.default(t)),
] ]
if (tabs.length < 2) {
return null
}
return ( return (
<nav className="trash-page-tabs" aria-label={t('trash')}> <nav className="trash-page-tabs" aria-label={t('trash')}>
{tabs.map(tab => { {tabs.map(tab => {
@@ -53,7 +53,8 @@ body {
.navbar-header .navbar-logo { .navbar-header .navbar-logo {
@include media-breakpoint-up(md) { @include media-breakpoint-up(md) {
position: relative; position: relative;
top: var(--spacing-05); top: calc(var(--spacing-05) * 2);
left: var(--spacing-05);
} }
} }
@@ -105,61 +106,6 @@ body {
} }
} }
&.library-enabled {
.navbar-default .navbar-header .navbar-logo {
@include media-breakpoint-up(md) {
top: calc(var(--spacing-05) * 2);
left: var(--spacing-05);
}
}
.project-list-wrapper .project-list-sidebar-wrapper-react {
padding-top: calc(var(--spacing-08) - 1px);
.project-list-sidebar-scroll {
padding-top: 0;
padding-right: var(--spacing-05);
}
ul.project-list-filters {
margin-top: var(--spacing-05);
.sidebar-filter-button {
display: flex;
align-items: center;
padding-left: calc(var(--spacing-05) + 20px + var(--spacing-05));
}
.sidebar-filter-button-with-icon {
// Only the items with an icon are 44px tall: 20px of content between
// 12px paddings. The rest of the filters stay at 40px.
line-height: var(--line-height-02);
padding: var(--spacing-05);
// Without this the icon is an inline box sitting on the text
// baseline, which leaves descender space below it and stops
// align-items from centring it against the label.
.icon {
display: flex;
align-items: center;
}
svg {
margin-right: var(--spacing-05);
}
}
.dropdown-header {
padding-left: calc(var(--spacing-06) + 20px + var(--spacing-05));
}
> li.tag button.tag-name {
padding-left: calc(var(--spacing-06) + 20px + var(--spacing-05));
}
}
}
}
&.ds-nav-hides-top-navbar { &.ds-nav-hides-top-navbar {
// The sidebar shows the logo and account/help menus from md upwards, so // The sidebar shows the logo and account/help menus from md upwards, so
// the top navbar is only needed on mobile. // the top navbar is only needed on mobile.
@@ -210,7 +156,7 @@ body {
max-width: 320px; max-width: 320px;
min-width: 200px; min-width: 200px;
margin-top: var(--spacing-03); margin-top: var(--spacing-03);
padding: var(--spacing-08) 0 0 0; padding: calc(var(--spacing-08) - 1px) 0 0;
.new-project-dropdown { .new-project-dropdown {
padding: 0 var(--spacing-08) var(--spacing-05) var(--spacing-05); padding: 0 var(--spacing-08) var(--spacing-05) var(--spacing-05);
@@ -240,8 +186,44 @@ body {
flex: 1 1 auto; flex: 1 1 auto;
overflow: auto; overflow: auto;
margin: 0 var(--spacing-02) 0 0; margin: 0 var(--spacing-02) 0 0;
padding: var(--spacing-05) var(--spacing-07) var(--spacing-05) padding: 0 var(--spacing-05) var(--spacing-05);
var(--spacing-05); }
ul.project-list-filters {
margin-top: var(--spacing-05);
.sidebar-filter-button {
display: flex;
align-items: center;
padding-left: calc(var(--spacing-05) + 20px + var(--spacing-05));
}
.sidebar-filter-button-with-icon {
// Only the items with an icon are 44px tall: 20px of content between
// 12px paddings. The rest of the filters stay at 40px.
line-height: var(--line-height-02);
padding: var(--spacing-05);
// Without this the icon is an inline box sitting on the text
// baseline, which leaves descender space below it and stops
// align-items from centring it against the label.
.icon {
display: flex;
align-items: center;
}
svg {
margin-right: var(--spacing-05);
}
}
.dropdown-header {
padding-left: calc(var(--spacing-06) + 20px + var(--spacing-05));
}
> li.tag button.tag-name {
padding-left: calc(var(--spacing-06) + 20px + var(--spacing-05));
}
} }
.ds-nav-page-switcher-logo { .ds-nav-page-switcher-logo {
-2
View File
@@ -579,7 +579,6 @@
"file_too_large": "For stor fil", "file_too_large": "For stor fil",
"files_cannot_include_invalid_characters": "Filnavnet er tomt, eller indeholder ugyldige karakterer", "files_cannot_include_invalid_characters": "Filnavnet er tomt, eller indeholder ugyldige karakterer",
"files_selected": "filer valgt.", "files_selected": "filer valgt.",
"filter_projects": "Filtrer projekter",
"filters": "Filtre", "filters": "Filtre",
"find_out_more": "Find ud af mere", "find_out_more": "Find ud af mere",
"find_out_more_about_institution_login": "Få mere at vide om institutionel indlogning", "find_out_more_about_institution_login": "Få mere at vide om institutionel indlogning",
@@ -1613,7 +1612,6 @@
"search_in_your_projects": "Søg i dine projekter", "search_in_your_projects": "Søg i dine projekter",
"search_match_case": "Match store/små bogstaver", "search_match_case": "Match store/små bogstaver",
"search_next": "næste", "search_next": "næste",
"search_only_the_bib_files_in_your_project_only_by_citekeys": "Søg udelukkende .bib filerne i dit projekt, kun efter citeringsnøgle.",
"search_previous": "forrige", "search_previous": "forrige",
"search_projects": "Søg efter projekter", "search_projects": "Søg efter projekter",
"search_references": "Søg i .bib filerne fra dette projekt", "search_references": "Søg i .bib filerne fra dette projekt",
-1
View File
@@ -559,7 +559,6 @@
"file_too_large": "Datei zu groß", "file_too_large": "Datei zu groß",
"files_cannot_include_invalid_characters": "Der Dateiname ist leer oder enthält ungültige Zeichen", "files_cannot_include_invalid_characters": "Der Dateiname ist leer oder enthält ungültige Zeichen",
"files_selected": "Dateien ausgewählt.", "files_selected": "Dateien ausgewählt.",
"filter_projects": "Projekte filtern",
"filters": "Filter", "filters": "Filter",
"find_out_more": "Finde mehr heraus", "find_out_more": "Finde mehr heraus",
"find_out_more_about_institution_login": "Erfahre mehr über den institutionellen Login", "find_out_more_about_institution_login": "Erfahre mehr über den institutionellen Login",
-2
View File
@@ -962,7 +962,6 @@
"file_tree": "File tree", "file_tree": "File tree",
"files_cannot_include_invalid_characters": "File name is empty or contains invalid characters", "files_cannot_include_invalid_characters": "File name is empty or contains invalid characters",
"files_selected": "files selected.", "files_selected": "files selected.",
"filter_projects": "Filter projects",
"filters": "Filters", "filters": "Filters",
"find": "Find", "find": "Find",
"find_out_how_to_get_the_most_out_of_your_new_subscription": "Find out how to get the most out of your new subscription by checking out <0>Overleaf’s AI features</0>.", "find_out_how_to_get_the_most_out_of_your_new_subscription": "Find out how to get the most out of your new subscription by checking out <0>Overleaf’s AI features</0>.",
@@ -2467,7 +2466,6 @@
"search_match_case": "Match case", "search_match_case": "Match case",
"search_members": "Search members", "search_members": "Search members",
"search_next": "next", "search_next": "next",
"search_only_the_bib_files_in_your_project_only_by_citekeys": "Search only the .bib files in your project, only by citekeys.",
"search_previous": "previous", "search_previous": "previous",
"search_project": "Search project", "search_project": "Search project",
"search_projects": "Search projects", "search_projects": "Search projects",
-2
View File
@@ -673,7 +673,6 @@
"file_tree": "文件树", "file_tree": "文件树",
"files_cannot_include_invalid_characters": "文件名为空或包含无效字符", "files_cannot_include_invalid_characters": "文件名为空或包含无效字符",
"files_selected": "个文件被选中。", "files_selected": "个文件被选中。",
"filter_projects": "过滤项目",
"filters": "筛选器", "filters": "筛选器",
"find": "查找", "find": "查找",
"find_out_more": "了解更多", "find_out_more": "了解更多",
@@ -1824,7 +1823,6 @@
"search_in_your_projects": "在您的项目中搜索", "search_in_your_projects": "在您的项目中搜索",
"search_match_case": "区分大小写", "search_match_case": "区分大小写",
"search_next": "下一个", "search_next": "下一个",
"search_only_the_bib_files_in_your_project_only_by_citekeys": "仅通过 citekeys 搜索项目中的 .bib 文件。",
"search_previous": "上一个", "search_previous": "上一个",
"search_project": "搜索项目", "search_project": "搜索项目",
"search_projects": "搜索项目", "search_projects": "搜索项目",
@@ -33,6 +33,19 @@ const {
const userId = owner.id const userId = owner.id
// The sidebar's trash page switcher and the per-row trash action buttons share
// the accessible name "Trash", so pick the one outside the project table.
function getSidebarTrashButton() {
const table = screen.getByRole('table')
return screen
.getAllByRole('button', { name: 'Trash' })
.filter(button => !table.contains(button))[0]
}
function getSidebarProjectsFilterButton() {
return screen.getAllByRole('button', { name: 'Projects' })[0]
}
describe('<ProjectListRoot />', function () { describe('<ProjectListRoot />', function () {
this.timeout('10s') this.timeout('10s')
@@ -308,8 +321,7 @@ describe('<ProjectListRoot />', function () {
describe('trashed projects', function () { describe('trashed projects', function () {
beforeEach(function () { beforeEach(function () {
const filterButton = screen.getAllByText('Trashed projects')[0] fireEvent.click(getSidebarTrashButton())
fireEvent.click(filterButton)
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox') allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
// + 1 because of select all // + 1 because of select all
@@ -326,15 +338,14 @@ describe('<ProjectListRoot />', function () {
}) })
it('shows the download, archive, and restore buttons in top toolbar', function () { it('shows the download, archive, and restore buttons in top toolbar', function () {
expect(screen.queryByLabelText('Trash')).to.be.null expect(within(actionsToolbar).queryByLabelText('Trash')).to.be.null
within(actionsToolbar).queryByLabelText('Download') within(actionsToolbar).queryByLabelText('Download')
within(actionsToolbar).queryByLabelText('Archive') within(actionsToolbar).queryByLabelText('Archive')
within(actionsToolbar).getByText('Restore') // no icon for this button within(actionsToolbar).getByText('Restore') // no icon for this button
}) })
it('clears selected projects when filter changed', function () { it('clears selected projects when filter changed', function () {
const filterButton = screen.getAllByText('All projects')[0] fireEvent.click(getSidebarProjectsFilterButton())
fireEvent.click(filterButton)
const allCheckboxes = const allCheckboxes =
screen.getAllByRole<HTMLInputElement>('checkbox') screen.getAllByRole<HTMLInputElement>('checkbox')
@@ -641,7 +652,10 @@ describe('<ProjectListRoot />', function () {
expect(screen.queryByText(p.name)).to.be.null expect(screen.queryByText(p.name)).to.be.null
}) })
const trashBtns = screen.getAllByRole('button', { name: 'Trash' }) const trashBtns = within(screen.getByRole('table')).getAllByRole(
'button',
{ name: 'Trash' }
)
for (const [index, trashBtn] of trashBtns.entries()) { for (const [index, trashBtn] of trashBtns.entries()) {
fireEvent.click(trashBtn) fireEvent.click(trashBtn)
fireEvent.click( fireEvent.click(
@@ -829,9 +843,7 @@ describe('<ProjectListRoot />', function () {
assertToolbarButtonsExists() assertToolbarButtonsExists()
// select trashed projects // select trashed projects
const [trashedProjectsButton] = fireEvent.click(getSidebarTrashButton())
screen.getAllByText(/trashed projects/i)
fireEvent.click(trashedProjectsButton)
fireEvent.click(tag) fireEvent.click(tag)
allCheckboxes = screen.getAllByRole('checkbox') allCheckboxes = screen.getAllByRole('checkbox')
@@ -843,8 +855,7 @@ describe('<ProjectListRoot />', function () {
describe('"More" dropdown', function () { describe('"More" dropdown', function () {
beforeEach(async function () { beforeEach(async function () {
const filterButton = screen.getAllByText('All projects')[0] fireEvent.click(getSidebarProjectsFilterButton())
fireEvent.click(filterButton)
allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox') allCheckboxes = screen.getAllByRole<HTMLInputElement>('checkbox')
}) })
@@ -41,9 +41,6 @@ describe('<SidebarDsNav />', function () {
global.localStorage.clear() global.localStorage.clear()
window.metaAttributesCache.set('ol-tags', []) window.metaAttributesCache.set('ol-tags', [])
window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: true }) window.metaAttributesCache.set('ol-ExposedSettings', { isOverleaf: true })
window.metaAttributesCache.set('ol-splitTestVariants', {
'overleaf-library': 'enabled',
})
window.metaAttributesCache.set('ol-inactiveTutorials', [ window.metaAttributesCache.set('ol-inactiveTutorials', [
'library-new-badge', 'library-new-badge',
]) ])