Improve landmarks on the Project dashboard and Editor pages (#26751)

* Improve landmarks for the Project Dasbhboard

* Improve landmarks for the IDE page

* Improve landmarks for the new redesigned IDE page

* Sort locales

* Fix typo OlButtonToolbar -> OLButtonToolbar

* Update project navbar translation

* Update labels

* Redundant main landmark

* Fix failing test

* Descriptive name for the rails tab

* Header should not be in a button

* Update translation to Account and help

* Update translation to Project categories and tags

* Add explanations

GitOrigin-RevId: 96712b37bb1306b552de510d9fd8ae890f24309f
This commit is contained in:
Rebeka Dekany
2025-07-23 09:25:12 +02:00
committed by Copybot
parent bd9fdfe99e
commit 1b4cbd4efb
41 changed files with 331 additions and 166 deletions

View File

@@ -85,7 +85,11 @@ describe('git-bridge', function () {
it('should render the git-bridge UI in the editor', function () {
maybeClearAllTokens()
createProject('git').as('projectId')
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Sync')
cy.findByText('Git').click()
cy.findByTestId('git-bridge-modal').within(() => {
@@ -100,7 +104,11 @@ describe('git-bridge', function () {
// Re-open
cy.url().then(url => cy.visit(url))
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Git').click()
cy.findByTestId('git-bridge-modal').within(() => {
cy.get('@projectId').then(id => {
@@ -188,7 +196,11 @@ describe('git-bridge', function () {
function checkGitAccess(access: 'readOnly' | 'readAndWrite') {
const recompile = throttledRecompile()
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Sync')
cy.findByText('Git').click()
cy.get('@projectId').then(projectId => {
@@ -370,7 +382,11 @@ Hello world
it('should not render the git-bridge UI in the editor', function () {
login('user@example.com')
createProject('maybe git')
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Word Count') // wait for lazy loading
cy.findByText('Sync').should('not.exist')
cy.findByText('Git').should('not.exist')

View File

@@ -37,7 +37,11 @@ describe('SandboxedCompiles', function () {
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2023\) /)
cy.log('Switch TeXLive version from 2023 to 2022')
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText(LABEL_TEX_LIVE_VERSION)
.parent()
.findByText('2023')
@@ -213,7 +217,11 @@ describe('SandboxedCompiles', function () {
cy.findByText(/This is pdfTeX/)
cy.log('Switch compiler to from pdfLaTeX to XeLaTeX')
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Compiler')
.parent()
.findByText('pdfLaTeX')
@@ -245,7 +253,11 @@ describe('SandboxedCompiles', function () {
cy.findByText(/This is pdfTeX, Version .+ \(TeX Live 2025\) /)
cy.log('Check that there is no TeX Live version toggle')
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Word Count') // wait for lazy loading
cy.findByText(LABEL_TEX_LIVE_VERSION).should('not.exist')
})

View File

@@ -57,7 +57,11 @@ describe('Templates', () => {
cy.visit('/')
createProject(name).as('templateProjectId')
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Manage Template').click()
cy.findByText('Template Description')
@@ -136,7 +140,11 @@ describe('Templates', () => {
cy.get('@templateProjectId').then(projectId =>
cy.visit(`/project/${projectId}`)
)
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Manage Template').click()
cy.findByText('Publish').click()
cy.findByText('Unpublish', { timeout: 10_000 })
@@ -161,7 +169,11 @@ describe('Templates', () => {
cy.findByText('Open as Template').click()
cy.url().should('match', /\/project\/[a-f0-9]{24}$/)
cy.get('.project-name').findByText(name)
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Word Count') // wait for lazy loading
cy.findByText('Manage Template').should('not.exist')
@@ -180,7 +192,11 @@ describe('Templates', () => {
cy.get('@templateProjectId').then(projectId =>
cy.visit(`/project/${projectId}`)
)
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Manage Template').click()
cy.findByText('Unpublish')
@@ -191,7 +207,11 @@ describe('Templates', () => {
cy.get('@templateProjectId').then(projectId =>
cy.visit(`/project/${projectId}`)
)
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Manage Template').click()
cy.findByText('Unpublish').click()
cy.findByText('Publish')
@@ -217,7 +237,11 @@ describe('Templates', () => {
cy.visit('/')
createProject('maybe templates')
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project actions/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Word Count') // wait for lazy loading
cy.findByText('Manage Template').should('not.exist')

View File

@@ -55,7 +55,11 @@ describe('Upgrading', function () {
cy.log('Trigger full flush')
recompile()
cy.get('header').findByText('Menu').click()
cy.findByRole('navigation', {
name: /Project Layout, Sharing, and Submission/i,
})
.findByRole('button', { name: /Menu/i })
.click()
cy.findByText('Source').click()
cy.get('.left-menu-modal-backdrop').click({ force: true })
}
@@ -116,7 +120,9 @@ describe('Upgrading', function () {
openProjectByName(PROJECT_NAME)
cy.url().should('match', /\/project\/[a-fA-F0-9]{24}/)
cy.findByRole('navigation').within(() => {
cy.findByRole('navigation', {
name: /Project actions/i,
}).within(() => {
cy.findByText(PROJECT_NAME)
})
const recompile = throttledRecompile()

View File

@@ -1,8 +1,5 @@
footer.fat-footer.hidden-print.website-redesign-fat-footer
.fat-footer-container(
role='navigation'
aria-label=translate('footer_navigation')
)
.fat-footer-container
.fat-footer-sections(class={hidden: hideFatFooter})
#footer-brand.footer-section
a.footer-brand(href='/' aria-label=settings.appName)

View File

@@ -1,8 +1,5 @@
footer.fat-footer.hidden-print
.fat-footer-container(
role='navigation'
aria-label=translate('footer_navigation')
)
.fat-footer-container
.fat-footer-sections(class={hidden: hideFatFooter})
#footer-brand.footer-section
a.footer-brand(href='/' aria-label=settings.appName)

View File

@@ -5,6 +5,7 @@ nav.navbar.navbar-default.navbar-main.navbar-expand-lg(
class={
'website-redesign-navbar': isWebsiteRedesign,
}
aria-label=translate('primary')
)
.container-fluid.navbar-container
.navbar-header

View File

@@ -2,6 +2,7 @@ nav.navbar.navbar-default.navbar-main(
class={
'website-redesign-navbar': isWebsiteRedesign,
}
aria-label=translate('primary')
)
.container-fluid
.navbar-header

View File

@@ -1,4 +1,6 @@
nav.navbar.navbar-default.navbar-main.website-redesign-navbar
nav.navbar.navbar-default.navbar-main.website-redesign-navbar(
aria-label=translate('primary')
)
.container-fluid
.navbar-header
if typeof suppressNavbarRight == 'undefined'

View File

@@ -12,7 +12,7 @@ block entrypointVar
- entrypoint = 'pages/ide'
block content
main#ide-root
#ide-root
.loading-screen
.loading-screen-brand-container
.loading-screen-brand(style='height: 20%')

View File

@@ -53,6 +53,7 @@
"account_billed_manually": "",
"account_has_been_link_to_institution_account": "",
"account_has_past_due_invoice_change_plan_warning": "",
"account_help": "",
"account_managed_by_group_administrator": "",
"account_not_linked_to_dropbox": "",
"account_settings": "",
@@ -566,6 +567,7 @@
"fast": "",
"fast_draft": "",
"features_like_track_changes": "",
"feedback": "",
"figure": "",
"file": "",
"file_action_created": "",
@@ -585,6 +587,7 @@
"file_size": "",
"file_tree": "",
"files_cannot_include_invalid_characters": "",
"files_collaboration_integrations_logs": "",
"files_selected": "",
"filter_projects": "",
"find": "",
@@ -605,7 +608,6 @@
"font_size": "",
"footer_about_us": "",
"footer_contact_us": "",
"footer_navigation": "",
"footnotes": "",
"for_business": "",
"for_government": "",
@@ -729,6 +731,7 @@
"headers": "",
"help": "",
"help_articles_matching": "",
"help_editor_settings": "",
"help_improve_overleaf_fill_out_this_survey": "",
"help_improve_screen_reader_fill_out_this_survey": "",
"help_shape_the_future_of_overleaf": "",
@@ -997,7 +1000,6 @@
"main_bibliography_file_for_this_project": "",
"main_document": "",
"main_file_not_found": "",
"main_navigation": "",
"make_a_copy": "",
"make_email_primary_description": "",
"make_owner": "",
@@ -1136,6 +1138,7 @@
"not_a_student": "",
"not_managed": "",
"not_now": "",
"notification": "",
"notification_personal_and_group_subscriptions": "",
"notification_project_invite_accepted_message": "",
"notification_project_invite_message": "",
@@ -1220,6 +1223,7 @@
"payment_error_update_payment_method": "",
"payment_provider_unreachable_error": "",
"payment_summary": "",
"pdf": "",
"pdf_compile_in_progress_error": "",
"pdf_compile_rate_limit_hit": "",
"pdf_compile_try_again": "",
@@ -1229,6 +1233,7 @@
"pdf_only_hide_editor": "",
"pdf_preview": "",
"pdf_preview_error": "",
"pdf_preview_logs": "",
"pdf_rendering_error": "",
"pdf_unavailable_for_download": "",
"pdf_viewer": "",
@@ -1290,6 +1295,7 @@
"primarily_work_study_question_nonprofit_ngo": "",
"primarily_work_study_question_other": "",
"primarily_work_study_question_university_school": "",
"primary": "",
"primary_certificate": "",
"priority_support": "",
"privacy_and_terms": "",
@@ -1302,14 +1308,17 @@
"processing_uppercase": "",
"professional": "",
"progress_bar_percentage": "",
"project_actions": "",
"project_approaching_file_limit": "",
"project_categories_tags": "",
"project_figure_modal": "",
"project_files": "",
"project_files_history": "",
"project_files_outline": "",
"project_flagged_too_many_compiles": "",
"project_has_too_many_files": "",
"project_history_list": "",
"project_history_labels": "",
"project_last_published_at": "",
"project_layout_sharing_submission": "",
"project_linked_to": "",
"project_name": "",
"project_not_linked_to_github": "",

View File

@@ -72,7 +72,7 @@ const ChatPane = React.memo(function ChatPane() {
}
return (
<aside className="chat">
<aside className="chat" aria-label={t('chat')}>
<InfiniteScroll
atEnd={atEnd}
className="messages"

View File

@@ -88,11 +88,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
const shouldDisplayPublishButton = hasPublishPermissions && PublishButton
return (
<header
className="toolbar toolbar-header"
role="navigation"
aria-label={t('project_layout_sharing_submission')}
>
<nav className="toolbar toolbar-header" aria-label={t('project_actions')}>
<div className="toolbar-left">
<MenuButton onClick={onShowLeftMenuClick} />
{cobranding && cobranding.logoImgUrl && (
@@ -158,7 +154,7 @@ const ToolbarHeader = React.memo(function ToolbarHeader({
</>
)}
</div>
</header>
</nav>
)
})

View File

@@ -9,7 +9,7 @@ function ChangeList() {
const { t } = useTranslation()
return (
<aside className="change-list" aria-label={t('project_history_list')}>
<aside className="change-list" aria-label={t('project_history_labels')}>
<div className="history-header history-toggle-switch-container">
<ToggleSwitch labelsOnly={labelsOnly} setLabelsOnly={setLabelsOnly} />
</div>

View File

@@ -55,6 +55,8 @@ export const EditorAndPdf: FC = () => {
'ide-panel-group-resizing': resizing,
hidden: !editorIsOpen,
})}
tagName="section"
aria-label={t('editor')}
>
{selectedEntityCount === 0 && <NoSelectionPane />}
{selectedEntityCount === 1 && openEntity?.type === 'fileRef' && (
@@ -105,6 +107,8 @@ export const EditorAndPdf: FC = () => {
onCollapse={handlePdfPaneCollapse}
onExpand={handlePdfPaneExpand}
className="ide-react-panel"
tagName="section"
aria-label={t('pdf_preview_logs')}
>
<PdfPreview />
{/* ensure that "sync to code" is available in PDF only layout */}

View File

@@ -7,6 +7,7 @@ import { OutlineContainer } from '@/features/outline/components/outline-containe
import { useOutlinePane } from '@/features/ide-react/hooks/use-outline-pane'
import React, { ElementType } from 'react'
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
import { t } from 'i18next'
const editorSidebarComponents = importOverleafModules(
'editorSidebarComponents'
@@ -18,10 +19,11 @@ export default function EditorSidebar() {
const { outlineEnabled, outlinePanelRef } = useOutlinePane()
return (
<aside
<nav
className={classNames('ide-react-editor-sidebar', {
hidden: view === 'history',
})}
aria-label={t('project_files_outline')}
>
{editorSidebarComponents.map(
({ import: { default: Component }, path }) => (
@@ -53,6 +55,6 @@ export default function EditorSidebar() {
<OutlineContainer />
</Panel>
</PanelGroup>
</aside>
</nav>
)
}

View File

@@ -1,10 +1,13 @@
import React from 'react'
import { useTranslation } from 'react-i18next'
export function HistorySidebar() {
const { t } = useTranslation()
return (
<aside
<nav
id="history-file-tree"
className="ide-react-editor-sidebar history-file-tree"
aria-label={t('project_files_history')}
/>
)
}

View File

@@ -69,7 +69,7 @@ export const ChatPane = () => {
<div className="chat-panel">
<RailPanelHeader title={t('collaborator_chat')} />
<div className="chat-wrapper">
<aside className="chat">
<aside className="chat" aria-label={t('chat')}>
<InfiniteScroll
atEnd={atEnd}
className="messages"

View File

@@ -22,7 +22,7 @@ export default function IntegrationCard({
<div className="integrations-panel-card-contents">
{icon}
<div className="integrations-panel-card-inner">
<header className="integrations-panel-card-header">
<div className="integrations-panel-card-header">
<div className="integrations-panel-card-title">{title}</div>
{showPaywallBadge && (
<OLBadge
@@ -33,7 +33,7 @@ export default function IntegrationCard({
{t('premium')}
</OLBadge>
)}
</header>
</div>
<p className="integrations-panel-card-description">{description}</p>
</div>
</div>

View File

@@ -68,6 +68,8 @@ export default function MainLayout() {
})}
minSize={5}
defaultSize={50}
tagName="section"
aria-label={t('editor')}
>
<div className="ide-redesign-editor-container">
<EditorPanel />
@@ -108,6 +110,8 @@ export default function MainLayout() {
ref={pdfPanelRef}
onExpand={handlePdfPaneExpand}
onCollapse={handlePdfPaneCollapse}
tagName="section"
aria-label={t('pdf_preview')}
>
<PdfPreview />
{pdfLayout === 'flat' && view === 'pdf' && (

View File

@@ -1,14 +1,19 @@
import { memo } from 'react'
import OlButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
import { useTranslation } from 'react-i18next'
import OLButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
import PdfCompileButton from '@/features/pdf-preview/components/pdf-compile-button'
import PdfHybridDownloadButton from '@/features/pdf-preview/components/pdf-hybrid-download-button'
import { DetachedSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
import SwitchToEditorButton from '@/features/pdf-preview/components/switch-to-editor-button'
function PdfPreviewHybridToolbar() {
const { t } = useTranslation()
// TODO: add detached pdf logic
return (
<OlButtonToolbar className="toolbar toolbar-pdf toolbar-pdf-hybrid">
<OLButtonToolbar
className="toolbar toolbar-pdf toolbar-pdf-hybrid"
aria-label={t('pdf')}
>
<div className="toolbar-pdf-left">
<PdfCompileButton />
<PdfHybridDownloadButton />
@@ -19,7 +24,7 @@ function PdfPreviewHybridToolbar() {
<DetachedSynctexControl />
{/* TODO: should we have code check? */}
</div>
</OlButtonToolbar>
</OLButtonToolbar>
)
}

View File

@@ -14,7 +14,7 @@ export default function RailPanelHeader({
const { t } = useTranslation()
const { handlePaneCollapse } = useRailContext()
return (
<header className="rail-panel-header">
<div className="rail-panel-header">
<h4 className="rail-panel-title">{title}</h4>
<div className="rail-panel-header-actions">
@@ -33,6 +33,6 @@ export default function RailPanelHeader({
/>
</OLTooltip>
</div>
</header>
</div>
)
}

View File

@@ -258,7 +258,13 @@ export const RailLayout = () => {
onSelect={onTabSelect}
id="ide-rail-tabs"
>
<div className={classNames('ide-rail', { hidden: isHistoryView })}>
{/* The <Nav> element is a "div" and has a "role="tablist"".
But it should be identified as a navigation landmark.
Therefore, we nest them: the parent <nav> is the landmark, and its child gets the "role="tablist"". */}
<nav
className={classNames('ide-rail', { hidden: isHistoryView })}
aria-label={t('files_collaboration_integrations_logs')}
>
<Nav activeKey={selectedTab} className="ide-rail-tabs-nav">
{railTabs
.filter(({ hide }) => !hide)
@@ -274,11 +280,13 @@ export const RailLayout = () => {
/>
))}
<div className="flex-grow-1" />
{railActions?.map(action => (
<RailActionElement key={action.key} action={action} />
))}
<nav aria-label={t('help_editor_settings')}>
{railActions?.map(action => (
<RailActionElement key={action.key} action={action} />
))}
</nav>
</Nav>
</div>
</nav>
<Panel
id={
newErrorlogs

View File

@@ -1,3 +1,4 @@
import { useTranslation } from 'react-i18next'
import { ToolbarMenuBar } from './menu-bar'
import { ToolbarProjectTitle } from './project-title'
import { OnlineUsers } from './online-users'
@@ -23,6 +24,7 @@ export const Toolbar = () => {
const { view, restoreView } = useLayoutContext()
const { cobranding } = useEditorContext()
const { permissionsLevel } = useIdeReactContext()
const { t } = useTranslation()
const shouldDisplaySubmitButton =
(permissionsLevel === 'owner' || permissionsLevel === 'readAndWrite') &&
SubmitProjectButton
@@ -34,18 +36,18 @@ export const Toolbar = () => {
if (view === 'history') {
return (
<div className="ide-redesign-toolbar">
<nav className="ide-redesign-toolbar" aria-label={t('project_actions')}>
<div className="d-flex align-items-center">
<BackToEditorButton onClick={handleBackToEditorClick} />
</div>
<ToolbarProjectTitle />
<div /> {/* Empty div used for spacing */}
</div>
</nav>
)
}
return (
<div className="ide-redesign-toolbar">
<nav className="ide-redesign-toolbar" aria-label={t('project_actions')}>
<div className="ide-redesign-toolbar-menu">
<ToolbarLogos cobranding={cobranding} />
<ToolbarMenuBar />
@@ -62,6 +64,6 @@ export const Toolbar = () => {
<ShareProjectButton />
{getMeta('ol-showUpgradePrompt') && <UpgradeButton />}
</div>
</div>
</nav>
)
}

View File

@@ -39,7 +39,7 @@ const OutlinePane = React.memo<{
return (
<div className={headerClasses}>
<header className="outline-header">
<div className="outline-header">
<OutlineToggleButton
toggleExpanded={toggleExpanded}
expanded={expanded}
@@ -47,7 +47,7 @@ const OutlinePane = React.memo<{
isPartial={isPartial}
isTexFile={isTexFile}
/>
</header>
</div>
{isOpen && (
<div className="outline-body">
<OutlineRoot

View File

@@ -487,6 +487,21 @@ function PdfJsViewer({ url, pdfFile }: PdfJsViewerProps) {
const toolbarInfoLoaded =
rawScale !== null && page !== null && totalPages !== null
// Remove the 'region' role from each PDF page container.
// This prevents polluting the landmark navigation menu for every page,
// which creates a poor screen reader experience. Page navigation should be handled
// by the toolbar controls.
useEffect(() => {
if (!initialised || !pdfJsWrapper) return
const pageElements = pdfJsWrapper.container.querySelectorAll(
'div[data-page-number][role="region"]'
)
pageElements.forEach(element => {
element.removeAttribute('role')
})
}, [initialised, pdfJsWrapper])
/* eslint-disable jsx-a11y/no-noninteractive-tabindex */
/* eslint-disable jsx-a11y/no-noninteractive-element-interactions */
return (

View File

@@ -1,6 +1,6 @@
import { memo, useState, useEffect, useRef } from 'react'
import OlButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
import { useTranslation } from 'react-i18next'
import OLButtonToolbar from '@/features/ui/components/ol/ol-button-toolbar'
import { useLayoutContext } from '@/shared/context/layout-context'
import PdfCompileButton from './pdf-compile-button'
import SwitchToEditorButton from './switch-to-editor-button'
@@ -15,7 +15,7 @@ const ORPHAN_UI_TIMEOUT_MS = 5000
function PdfPreviewHybridToolbar() {
const { detachRole, detachIsLinked } = useLayoutContext()
const { t } = useTranslation()
const uiTimeoutRef = useRef<number>()
const [orphanPdfTabAfterDelay, setOrphanPdfTabAfterDelay] = useState(false)
@@ -47,9 +47,12 @@ function PdfPreviewHybridToolbar() {
}
return (
<OlButtonToolbar className="toolbar toolbar-pdf toolbar-pdf-hybrid">
<OLButtonToolbar
className="toolbar toolbar-pdf toolbar-pdf-hybrid"
aria-label={t('pdf')}
>
{ToolbarInner}
</OlButtonToolbar>
</OLButtonToolbar>
)
}

View File

@@ -99,7 +99,7 @@ function PreviewLogEntryHeader({
const headerTitleText = logType ? `${logType} ${headerTitle}` : headerTitle
return (
<header className={logEntryHeaderClasses}>
<div className={logEntryHeaderClasses}>
{headerIcon ? (
<div className="log-entry-header-icon-container">{headerIcon}</div>
) : null}
@@ -116,7 +116,7 @@ function PreviewLogEntryHeader({
) : (
locationLink
)}
</header>
</div>
)
}

View File

@@ -1,4 +1,5 @@
import { JSXElementConstructor } from 'react'
import { useTranslation } from 'react-i18next'
import Common from './groups/common'
import Institution from './groups/institution'
import ConfirmEmail from './groups/confirm-email'
@@ -32,9 +33,13 @@ const USGovBanner: JSXElementConstructor<Record<string, never>> =
function UserNotifications() {
const groupSubscriptionsPendingEnrollment =
getMeta('ol-groupSubscriptionsPendingEnrollment') || []
const { t } = useTranslation()
return (
<div className="user-notifications notification-list">
<section
className="user-notifications notification-list"
aria-label={t('notification')}
>
<ul className="list-unstyled">
{EnrollmentNotification &&
groupSubscriptionsPendingEnrollment.map(subscription => (
@@ -58,7 +63,7 @@ function UserNotifications() {
{isDeprecatedBrowser() && <DeprecatedBrowser />}
</ul>
</div>
</section>
)
}

View File

@@ -62,73 +62,75 @@ export function ProjectListDsNav() {
overleafLogo={overleafLogo}
showCloseIcon
/>
<main className="project-list-wrapper">
<div className="project-list-wrapper">
<SidebarDsNav />
<div className="project-ds-nav-content-and-messages">
<div className="project-ds-nav-content">
<div className="project-ds-nav-main">
{error ? <DashApiError /> : ''}
<UserNotifications />
<div className="project-list-header-row">
<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 ? (
<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"
/>
<div className="project-tools">
<div className="d-none d-md-block">
{selectedProjects.length === 0 ? (
<CurrentPlanWidget />
) : (
<ProjectTools />
)}
</div>
<div className="d-md-none">
<CurrentPlanWidget />
) : (
<ProjectTools />
)}
</div>
<div className="d-md-none">
<CurrentPlanWidget />
</div>
</div>
</div>
</div>
<div className="project-ds-nav-project-list">
<OLRow className="d-none d-md-block">
<OLCol lg={7}>
<SearchForm
inputValue={searchText}
setInputValue={setSearchText}
filter={filter}
selectedTag={selectedTag}
/>
</OLCol>
</OLRow>
<div className="project-list-sidebar-survey-wrapper d-md-none">
{/* Omit the survey card in mobile view for now */}
</div>
<div className="mt-1 d-md-none">
<div
role="toolbar"
className="projects-toolbar"
aria-label={t('projects')}
>
<ProjectsDropdown />
<SortByDropdown />
<div className="project-ds-nav-project-list">
<OLRow className="d-none d-md-block">
<OLCol lg={7}>
<SearchForm
inputValue={searchText}
setInputValue={setSearchText}
filter={filter}
selectedTag={selectedTag}
/>
</OLCol>
</OLRow>
<div className="project-list-sidebar-survey-wrapper d-md-none">
{/* Omit the survey card in mobile view for now */}
</div>
<div className="mt-1 d-md-none">
<div
role="toolbar"
className="projects-toolbar"
aria-label={t('projects')}
>
<ProjectsDropdown />
<SortByDropdown />
</div>
</div>
<div className="mt-3">
<TableContainer bordered>
{tableTopArea}
<ProjectListTable />
</TableContainer>
</div>
<div className="mt-3">
<LoadMore />
</div>
</div>
<div className="mt-3">
<TableContainer bordered>
{tableTopArea}
<ProjectListTable />
</TableContainer>
</div>
<div className="mt-3">
<LoadMore />
</div>
</div>
</main>
</div>
<Footer {...footerProps} />
</div>
<CookieBanner />
</div>
</main>
</div>
</div>
)
}

View File

@@ -50,8 +50,8 @@ function DefaultNavbarAndFooter({ children }: { children: ReactNode }) {
<>
<DefaultNavbar {...navbarProps} />
<main
id="main-content"
className="content content-alt project-list-react"
aria-labelledby="main-content"
>
{children}
</main>

View File

@@ -1,4 +1,5 @@
import { useState } from 'react'
import { useTranslation } from 'react-i18next'
import classnames from 'classnames'
import { Question, User } from '@phosphor-icons/react'
import NewProjectButton from '../new-project-button'
@@ -8,7 +9,6 @@ import { usePersistedResize } from '@/shared/hooks/use-resize'
import { Dropdown } from 'react-bootstrap'
import getMeta from '@/utils/meta'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
import { useTranslation } from 'react-i18next'
import { NavDropdownMenuItems } from '@/features/ui/components/bootstrap-5/navbar/nav-dropdown-from-data'
import { NavbarDropdownItemData } from '@/features/ui/components/types/navbar'
import { useContactUsModal } from '@/shared/hooks/use-contact-us-modal'
@@ -45,29 +45,34 @@ function SidebarDsNav() {
},
})}
>
<NewProjectButton
id="new-project-button-sidebar"
className={scrolledDown ? 'show-shadow' : undefined}
/>
<div
className="project-list-sidebar-scroll"
ref={containerRef}
data-testid="project-list-sidebar-scroll"
>
<SidebarFilters />
{showAddAffiliationWidget && <hr />}
<AddAffiliation />
</div>
<nav aria-label={t('project_categories_tags')}>
<NewProjectButton
id="new-project-button-sidebar"
className={scrolledDown ? 'show-shadow' : undefined}
/>
<div
className="project-list-sidebar-scroll"
ref={containerRef}
data-testid="project-list-sidebar-scroll"
>
<SidebarFilters />
{showAddAffiliationWidget && <hr />}
<AddAffiliation />
</div>
</nav>
<div
className={classnames(
'ds-nav-sidebar-lower',
scrolledUp && 'show-shadow'
)}
>
<div className="project-list-sidebar-survey-wrapper">
<aside
className="project-list-sidebar-survey-wrapper"
aria-label={t('feedback')}
>
<SurveyWidgetDsNav />
</div>
<div className="d-flex gap-3 mb-2">
</aside>
<nav className="d-flex gap-3 mb-2" aria-label={t('account_help')}>
{helpItem && (
<Dropdown
className="ds-nav-icon-dropdown"
@@ -157,7 +162,7 @@ function SidebarDsNav() {
<UserProvider>{contactUsModal}</UserProvider>
</>
)}
</div>
</nav>
<div className="ds-nav-ds-name" translate="no">
<span>Digital Science</span>
</div>

View File

@@ -44,12 +44,14 @@ function ProjectListTitle({
}
return (
<div
<h1
id="main-content"
tabIndex={-1}
className={classnames('project-list-title', className)}
{...extraProps}
>
{message}
</div>
</h1>
)
}

View File

@@ -23,7 +23,7 @@ function FatFooterBase() {
const currentYear = new Date().getFullYear()
return (
<footer className="fat-footer-base">
<div className="fat-footer-base">
<div className="fat-footer-base-section fat-footer-base-meta">
<div className="fat-footer-base-item">
<div className="fat-footer-base-copyright" translate="no">
@@ -62,7 +62,7 @@ function FatFooterBase() {
/>
</div>
</div>
</footer>
</div>
)
}

View File

@@ -91,11 +91,7 @@ function FatFooter() {
return (
<footer className="fat-footer hidden-print">
<div
role="navigation"
aria-label={t('footer_navigation')}
className="fat-footer-container"
>
<div className="fat-footer-container">
<div className={`fat-footer-sections ${hideFatFooter ? 'hidden' : ''}`}>
<div className="footer-section" id="footer-brand">
<a href="/" aria-label={t('overleaf')} className="footer-brand">

View File

@@ -61,6 +61,7 @@ function DefaultNavbar(
'--navbar-brand-image-redesign-url': `url("${overleafBlackLogo}")`,
} as CSSPropertiesWithVariables
}
aria-label={t('primary')}
>
<Container className="navbar-container" fluid>
<div className="navbar-header">
@@ -92,7 +93,7 @@ function DefaultNavbar(
<Navbar.Toggle
aria-controls="navbar-main-collapse"
aria-expanded="false"
aria-label={t('main_navigation')}
aria-label={t('primary')}
>
{showCloseIcon && expanded ? (
<X />

View File

@@ -140,15 +140,14 @@
align-items: center;
margin-bottom: var(--spacing-05);
min-height: 36px;
}
.project-list-title {
min-width: 0;
color: $content-secondary;
.project-list-title {
@include heading-sm;
@include heading-sm;
font-weight: bold;
color: $content-secondary;
font-weight: bold;
min-width: 0;
}
}
.project-tools {

View File

@@ -59,6 +59,7 @@
"account_billed_manually": "Account billed manually",
"account_has_been_link_to_institution_account": "Your __appName__ account on <b>__email__</b> has been linked to your <b>__institutionName__</b> institutional account.",
"account_has_past_due_invoice_change_plan_warning": "Your account currently has a past due invoice. You will not be able to change your plan until this is resolved.",
"account_help": "Account and help",
"account_linking": "Account linking",
"account_managed_by_group_administrator": "Your account is managed by your group administrator (__admin__)",
"account_not_linked_to_dropbox": "Your account is not linked to Dropbox",
@@ -744,6 +745,7 @@
"features": "Features",
"features_like_track_changes": "Features like real-time track changes",
"february": "February",
"feedback": "Feedback",
"figure": "Figure",
"file": "File",
"file_action_created": "Created",
@@ -764,6 +766,7 @@
"file_too_large": "File too large",
"file_tree": "File tree",
"files_cannot_include_invalid_characters": "File name is empty or contains invalid characters",
"files_collaboration_integrations_logs": "Files, collaboration, integrations, error logs",
"files_selected": "files selected.",
"filter_projects": "Filter projects",
"filters": "Filters",
@@ -787,7 +790,6 @@
"font_size": "Font Size",
"footer_about_us": "About us",
"footer_contact_us": "Contact us",
"footer_navigation": "Footer navigation",
"footnotes": "Footnotes",
"for_business": "For business",
"for_government": "For government",
@@ -949,6 +951,7 @@
"help": "Help",
"help_and_resources": "Help & resources",
"help_articles_matching": "Help articles matching your subject",
"help_editor_settings": "Help and editor settings",
"help_guides": "Help guides",
"help_improve_overleaf_fill_out_this_survey": "If you would like to help us improve Overleaf, please take a moment to fill out <0>this survey</0>.",
"help_improve_screen_reader_fill_out_this_survey": "Help us improve your experience using a screen reader with __appName__ by filling out this quick survey.",
@@ -1303,7 +1306,6 @@
"main_bibliography_file_for_this_project": "Main bibliography file for this project",
"main_document": "Main document",
"main_file_not_found": "Unknown main document",
"main_navigation": "Main navigation",
"maintenance": "Maintenance",
"make_a_copy": "Make a copy",
"make_email_primary_description": "Make this the primary email, used to log in",
@@ -1479,6 +1481,7 @@
"not_now": "Not now",
"not_registered": "Not registered",
"note_features_under_development": "<0>Please note</0> that features in this program are still being tested and actively developed. This means that they might <0>change</0>, be <0>removed</0> or <0>become part of a premium plan</0>",
"notification": "Notification",
"notification_features_upgraded_by_affiliation": "Good news! Your affiliated organization __institutionName__ has an Overleaf subscription, and you now have access to all of Overleafs Professional features.",
"notification_personal_and_group_subscriptions": "Weve spotted that youve got <0>more than one active __appName__ subscription</0>. To avoid paying more than you need to, <1>review your subscriptions</1>.",
"notification_personal_subscription_not_required_due_to_affiliation": " Good news! Your affiliated organization __institutionName__ has an Overleaf subscription, and you now have access to Overleafs Professional features through your affiliation. You can cancel your individual subscription without losing access to any features.",
@@ -1615,6 +1618,7 @@
"pdf_only_hide_editor": "PDF only <0>(hide editor)</0>",
"pdf_preview": "PDF preview",
"pdf_preview_error": "There was a problem displaying the compilation results for this project.",
"pdf_preview_logs": "PDF preview and logs",
"pdf_rendering_error": "PDF Rendering Error",
"pdf_unavailable_for_download": "PDF unavailable for download",
"pdf_viewer": "PDF Viewer",
@@ -1695,6 +1699,7 @@
"primarily_work_study_question_nonprofit_ngo": "Nonprofit or NGO",
"primarily_work_study_question_other": "Other",
"primarily_work_study_question_university_school": "University or school",
"primary": "Primary",
"primary_certificate": "Primary certificate",
"primary_email_check_question": "Is <0>__email__</0> still your email address?",
"priority_support": "Priority support",
@@ -1714,14 +1719,17 @@
"professional": "Professional",
"progress_bar_percentage": "Progress bar from 0 to 100%",
"project": "project",
"project_actions": "Project actions",
"project_approaching_file_limit": "This project is approaching the file limit",
"project_categories_tags": "Project categories and tags",
"project_figure_modal": "Project",
"project_files": "Project files",
"project_files_history": "Project files history",
"project_files_outline": "Project files and outline",
"project_flagged_too_many_compiles": "This project has been flagged for compiling too often. The limit will be lifted shortly.",
"project_has_too_many_files": "This project has reached the 2000 file limit",
"project_history_list": "Project history list",
"project_history_labels": "Project history and labels",
"project_last_published_at": "Your project was last published at",
"project_layout_sharing_submission": "Project Layout, Sharing, and Submission",
"project_linked_to": "This project is linked to",
"project_name": "Project Name",
"project_not_linked_to_github": "This project is not linked to a GitHub repository. You can create a repository for it in GitHub:",

View File

@@ -31,10 +31,12 @@ describe('<PdfJSViewer/>', function () {
cy.waitForCompile({ pdf: true })
cy.findByRole('region', { name: `Page ${FSI}1${PDI}` })
cy.findByRole('region', { name: `Page ${FSI}2${PDI}` })
cy.findByRole('region', { name: `Page ${FSI}3${PDI}` })
cy.findByRole('region', { name: `Page ${FSI}4${PDI}` }).should('not.exist')
cy.findByTestId('pdfjs-viewer-inner').within(() => {
cy.findByLabelText(`Page ${FSI}1${PDI}`)
cy.findByLabelText(`Page ${FSI}2${PDI}`)
cy.findByLabelText(`Page ${FSI}3${PDI}`)
cy.findByLabelText(`Page ${FSI}4${PDI}`).should('not.exist')
})
cy.contains('Your Paper')
})
@@ -96,7 +98,9 @@ describe('<PdfJSViewer/>', function () {
cy.waitForCompile({ pdf: true })
cy.findByRole('region', { name: `Page ${FSI}1${PDI}` })
cy.findByTestId('pdfjs-viewer-inner').within(() => {
cy.findByLabelText(`Page ${FSI}1${PDI}`)
})
cy.then(() => unmountComponentAtNode(getContainerEl()))
})

View File

@@ -144,7 +144,19 @@ describe('change list (Bootstrap 5)', function () {
cy.findByRole('button', { name: /delete/i }).should('not.exist')
})
)
cy.findByLabelText(/labels/i).click({ force: true })
cy.findByRole('complementary', {
name: /Project history and labels/i,
}).within(() => {
cy.findByRole('group', {
name: /Show all of the project history/i,
}).within(() => {
cy.findByText(/Labels/i).click()
})
cy.findByRole('radio', { name: /Labels/i }).should('be.checked')
cy.findByRole('radio', { name: /All history/i }).should(
'not.be.checked'
)
})
cy.findAllByTestId('history-version-details').as('details')
// first details on labels is always "current version", start testing on second
cy.get('@details').should('have.length', 3)
@@ -337,7 +349,19 @@ describe('change list (Bootstrap 5)', function () {
}
)
waitForData()
cy.findByLabelText(/labels/i).click({ force: true })
cy.findByRole('complementary', {
name: /Project history and labels/i,
}).within(() => {
cy.findByRole('group', {
name: /Show all of the project history/i,
}).within(() => {
cy.findByText(/Labels/i).click()
})
cy.findByRole('radio', { name: /Labels/i }).should('be.checked')
cy.findByRole('radio', { name: /All history/i }).should(
'not.be.checked'
)
})
})
it('shows the dropdown menu item for adding new labels', function () {
@@ -688,7 +712,19 @@ describe('change list (Bootstrap 5)', function () {
waitForData()
cy.findByLabelText(/labels/i).click({ force: true })
cy.findByRole('complementary', {
name: /Project history and labels/i,
}).within(() => {
cy.findByRole('group', {
name: /Show all of the project history/i,
}).within(() => {
cy.findByText(/Labels/i).click()
})
cy.findByRole('radio', { name: /Labels/i }).should('be.checked')
cy.findByRole('radio', { name: /All history/i }).should(
'not.be.checked'
)
})
// One pseudo-label for the current state, one for our label
cy.get('.history-version-label').should('have.length', 2)