diff --git a/server-ce/test/editor.spec.ts b/server-ce/test/editor.spec.ts index 1568b12f8d..79013bdc87 100644 --- a/server-ce/test/editor.spec.ts +++ b/server-ce/test/editor.spec.ts @@ -2,6 +2,8 @@ import { createNewFile, createProjectAndOpenInNewEditor, openProjectById, + openProjectByName, + shareProjectByEmailAndAcceptInviteViaDash, testNewFileUpload, } from './helpers/project' import { isExcludedBySharding, startWith } from './helpers/config' @@ -321,6 +323,124 @@ describe('editor', function () { }) }) +describe('editing mode', function () { + if (isExcludedBySharding('PRO_DEFAULT_1')) return + startWith({ pro: true }) + + const OWNER = USER + const EDITOR = COLLABORATOR + const REVIEWER = 'reviewer@example.com' + const VIEWER = 'viewer@example.com' + ensureUserExists({ email: OWNER }) + ensureUserExists({ email: EDITOR }) + ensureUserExists({ email: REVIEWER }) + ensureUserExists({ email: VIEWER }) + + let projectName: string + + function openEditingModeMenu() { + cy.findByRole('button', { name: 'View' }).click() + cy.findByRole('menuitem', { name: 'Editing mode' }).click() + } + + beforeWithReRunOnTestRetry(() => { + projectName = `project-${uuid()}` + login(OWNER) + createProjectAndOpenInNewEditor(projectName, { type: 'Example project' }) + + shareProjectByEmailAndAcceptInviteViaDash( + projectName, + EDITOR, + 'Editor', + true + ) + login(OWNER) + shareProjectByEmailAndAcceptInviteViaDash( + projectName, + REVIEWER, + 'Reviewer', + true + ) + login(OWNER) + shareProjectByEmailAndAcceptInviteViaDash( + projectName, + VIEWER, + 'Viewer', + true + ) + }) + + it('lets an editor switch between editing and reviewing', function () { + login(EDITOR) + openProjectByName(projectName, true) + + openEditingModeMenu() + cy.findByRole('menuitem', { name: 'Editing' }) + .should('have.attr', 'aria-selected', 'true') + .and('not.have.attr', 'aria-disabled') + cy.findByRole('menuitem', { name: 'Reviewing' }) + .should('have.attr', 'aria-selected', 'false') + .and('not.have.attr', 'aria-disabled') + cy.findByRole('menuitem', { name: 'Viewing' }).should('not.exist') + + cy.findByRole('menuitem', { name: 'Reviewing' }).click() + cy.findByRole('button', { name: 'Reviewing' }).should('exist') + + openEditingModeMenu() + cy.findByRole('menuitem', { name: 'Reviewing' }).should( + 'have.attr', + 'aria-selected', + 'true' + ) + cy.findByRole('menuitem', { name: 'Editing' }).should( + 'have.attr', + 'aria-selected', + 'false' + ) + + cy.findByRole('menuitem', { name: 'Editing' }).click() + cy.findByRole('button', { name: 'Editing' }).should('exist') + }) + + it('shows a reviewer in reviewing mode with editing disabled', function () { + login(REVIEWER) + openProjectByName(projectName, true) + + openEditingModeMenu() + cy.findByRole('menuitem', { name: 'Reviewing' }) + .should('have.attr', 'aria-selected', 'true') + .and('not.have.attr', 'aria-disabled') + cy.findByRole('menuitem', { name: 'Editing' }).should( + 'have.attr', + 'aria-disabled', + 'true' + ) + cy.findByRole('menuitem', { name: 'Viewing' }).should('not.exist') + }) + + it('shows a viewer in viewing mode with editing and reviewing disabled', function () { + login(VIEWER) + openProjectByName(projectName, true) + + openEditingModeMenu() + cy.findByRole('menuitem', { name: 'Viewing' }).should( + 'have.attr', + 'aria-selected', + 'true' + ) + cy.findByRole('menuitem', { name: 'Editing' }).should( + 'have.attr', + 'aria-disabled', + 'true' + ) + cy.findByRole('menuitem', { name: 'Reviewing' }).should( + 'have.attr', + 'aria-disabled', + 'true' + ) + }) +}) + function createRandomLetterString() { const chars = 'abcdefghijklmnopqrstuvwxyz' let result = '' diff --git a/server-ce/test/helpers/project.ts b/server-ce/test/helpers/project.ts index 643a742e48..9c70947474 100644 --- a/server-ce/test/helpers/project.ts +++ b/server-ce/test/helpers/project.ts @@ -206,7 +206,7 @@ export function openProjectViaInviteNotification( function shareProjectByEmail( projectName: string, email: string, - level: 'Viewer' | 'Editor', + level: 'Viewer' | 'Editor' | 'Reviewer', newEditor: boolean = false ) { openProjectByName(projectName, newEditor) @@ -231,7 +231,7 @@ function shareProjectByEmail( export function shareProjectByEmailAndAcceptInviteViaDash( projectName: string, email: string, - level: 'Viewer' | 'Editor', + level: 'Viewer' | 'Editor' | 'Reviewer', newEditor: boolean = false ) { shareProjectByEmail(projectName, email, level, newEditor) diff --git a/services/web/frontend/extracted-translations.json b/services/web/frontend/extracted-translations.json index cf7732678b..f33654db52 100644 --- a/services/web/frontend/extracted-translations.json +++ b/services/web/frontend/extracted-translations.json @@ -585,6 +585,7 @@ "edited": "", "editing": "", "editing_captions": "", + "editing_mode": "", "editor": "", "editor_disconected_click_to_reconnect": "", "editor_font_family": "", @@ -592,6 +593,7 @@ "editor_limit_exceeded_in_this_project": "", "editor_line_height": "", "editor_only": "", + "editor_settings": "", "editor_theme": "", "editor_theme_dark": "", "editor_theme_light": "", @@ -1453,6 +1455,7 @@ "pdf_unavailable_for_download": "", "pdf_viewer": "", "pdf_viewer_error": "", + "pdf_zoom": "", "pdf_zoom_level": "", "pending": "", "pending_additional_licenses": "", diff --git a/services/web/frontend/js/features/ide-react/components/toolbar/menu-bar.tsx b/services/web/frontend/js/features/ide-react/components/toolbar/menu-bar.tsx index 1c28b52220..626eb56ead 100644 --- a/services/web/frontend/js/features/ide-react/components/toolbar/menu-bar.tsx +++ b/services/web/frontend/js/features/ide-react/components/toolbar/menu-bar.tsx @@ -26,6 +26,7 @@ import EditorCloneProjectModalWrapper from '@/features/clone-project-modal/compo import useOpenProject from '@/shared/hooks/use-open-project' import importOverleafModules from '../../../../../macros/import-overleaf-module.macro' import { useFeatureFlag } from '@/shared/context/split-test-context' +import ReviewModeOptions from './review-mode-options' const menubarExtraComponents = importOverleafModules( 'menubarExtraComponents' @@ -205,10 +206,16 @@ export const ToolbarMenuBar = () => { id: 'pdf-controls', children: [ 'view-pdf-presentation-mode', - 'view-pdf-zoom-in', - 'view-pdf-zoom-out', - 'view-pdf-fit-width', - 'view-pdf-fit-height', + { + id: 'pdf-zoom-control-group', + title: t('pdf_zoom'), + children: [ + 'view-pdf-zoom-in', + 'view-pdf-zoom-out', + 'view-pdf-fit-width', + 'view-pdf-fit-height', + ], + }, ], }), [t] @@ -262,8 +269,9 @@ export const ToolbarMenuBar = () => { className="ide-redesign-toolbar-dropdown-toggle-subdued ide-redesign-toolbar-button-subdued" > + - Editor settings + {t('editor_settings')} { + const { t } = useTranslation() + const { wantTrackChanges } = useEditorPropertiesContext() + const { write, trackedWrite } = usePermissionsContext() + const { permissionsLevel } = useIdeReactContext() + const { features } = useProjectContext() + const { setUpgradeTrackChangesModal } = useEditorContext() + const user = useUserContext() + + const mode = getMode(permissionsLevel, wantTrackChanges) + const showViewOption = mode === 'view' + + if (!features.trackChangesVisible) { + return null + } + + return ( + <> + + + { + if (mode === 'edit') { + return + } + sendMB('editing-mode-change', { + role: permissionsLevel, + previousMode: mode, + newMode: 'edit', + }) + window.dispatchEvent(new Event('toggle-track-changes')) + }} + leadingIcon="edit" + active={write && mode === 'edit'} + > + {t('editing')} + + { + if (mode === 'review') { + return + } + if (!features.trackChanges) { + setUpgradeTrackChangesModal({ + show: true, + location: 'menu-bar', + }) + } else { + sendMB('editing-mode-change', { + role: permissionsLevel, + previousMode: mode, + newMode: mode, + }) + window.dispatchEvent(new Event('toggle-track-changes')) + } + }} + leadingIcon="rate_review" + active={trackedWrite && mode === 'review'} + > + {t('reviewing')} + + {showViewOption && ( + + {t('viewing')} + + )} + + + ) +} + +export default ReviewModeOptions diff --git a/services/web/locales/en.json b/services/web/locales/en.json index 9fb065c00c..86669f001d 100644 --- a/services/web/locales/en.json +++ b/services/web/locales/en.json @@ -761,6 +761,7 @@ "editing": "Editing", "editing_and_collaboration": "Editing and collaboration", "editing_captions": "Editing captions", + "editing_mode": "Editing mode", "editing_tools": "Editing tools", "editor": "Editor", "editor_disconected_click_to_reconnect": "Editor disconnected, click anywhere to reconnect.", @@ -769,6 +770,7 @@ "editor_limit_exceeded_in_this_project": "Too many editors in this project", "editor_line_height": "Editor line height", "editor_only": "Editor only", + "editor_settings": "Editor settings", "editor_theme": "Editor theme", "editor_theme_dark": "Dark editor theme", "editor_theme_light": "Light editor theme", @@ -1911,6 +1913,7 @@ "pdf_unavailable_for_download": "PDF unavailable for download", "pdf_viewer": "PDF Viewer", "pdf_viewer_error": "There was a problem displaying the PDF for this project.", + "pdf_zoom": "PDF zoom", "pdf_zoom_level": "PDF zoom level", "pending": "Pending", "pending_additional_licenses": "Your subscription is changing to include <0>__pendingAdditionalLicenses__ additional license(s) for a total of <1>__pendingTotalLicenses__ licenses.",