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__0> additional license(s) for a total of <1>__pendingTotalLicenses__1> licenses.",