diff --git a/services/web/frontend/js/features/review-panel/components/review-mode-switcher.tsx b/services/web/frontend/js/features/review-panel/components/review-mode-switcher.tsx index 31ae4c74cb..7f377edb09 100644 --- a/services/web/frontend/js/features/review-panel/components/review-mode-switcher.tsx +++ b/services/web/frontend/js/features/review-panel/components/review-mode-switcher.tsx @@ -18,8 +18,9 @@ import { useProjectContext } from '@/shared/context/project-context' import { useCodeMirrorViewContext } from '@/features/source-editor/components/codemirror-context' import { useEditorContext } from '@/shared/context/editor-context' import { useTrackingChangesMode } from '@/shared/hooks/use-tracking-changes-mode' +import { useFeatureFlag } from '@/shared/context/split-test-context' -function ReviewModeSwitcher() { +function ReviewModeDropdownItems() { const { t } = useTranslation() const user = useUserContext() const { saveTrackChangesForCurrentUser, saveTrackChanges } = @@ -32,6 +33,108 @@ function ReviewModeSwitcher() { const showViewOption = permissionsLevel === 'readOnly' const view = useCodeMirrorViewContext() + return ( + <> + { + if (mode === 'edit') { + view.focus() + return + } + sendMB('editing-mode-change', { + role: permissionsLevel, + previousMode: mode, + newMode: 'edit', + }) + if (user?.id) { + saveTrackChangesForCurrentUser(false) + } else { + saveTrackChanges({ on_for_guests: false }) + } + view.focus() + }} + description={t('edit_content_directly')} + leadingIcon="edit" + active={write && mode === 'edit'} + > + {t('editing')} + + { + if (mode === 'review') { + view.focus() + return + } + if (!features.trackChanges) { + setUpgradeTrackChangesModal({ + show: true, + location: 'review-switcher', + }) + } else { + sendMB('editing-mode-change', { + role: permissionsLevel, + previousMode: mode, + newMode: 'review', + }) + if (user?.id) { + saveTrackChangesForCurrentUser(true) + } else { + saveTrackChanges({ on_for_guests: true }) + } + view.focus() + } + }} + description={ + permissionsLevel === 'review' && !trackedWrite + ? t('comment_only') + : t('edits_become_suggestions') + } + leadingIcon="rate_review" + active={trackedWrite && mode === 'review'} + > + {t('reviewing')} + + {showViewOption && ( + + {t('viewing')} + + )} + + ) +} + +function ReviewModeSwitcher() { + const isToolbarMigration = useFeatureFlag('writefull-toolbar-migration') + + if (isToolbarMigration) { + return ( + + + + + + + ) + } + + // Portal variant (default): sticky container keeps the button floating + // over the editor at top-right of the scroll area. return (
@@ -40,76 +143,7 @@ function ReviewModeSwitcher() { id="review-mode-switcher" /> - { - if (mode === 'edit') { - view.focus() - return - } - sendMB('editing-mode-change', { - role: permissionsLevel, - previousMode: mode, - newMode: 'edit', - }) - if (user?.id) { - saveTrackChangesForCurrentUser(false) - } else { - saveTrackChanges({ on_for_guests: false }) - } - view.focus() - }} - description={t('edit_content_directly')} - leadingIcon="edit" - active={write && mode === 'edit'} - > - {t('editing')} - - { - if (mode === 'review') { - view.focus() - return - } - if (!features.trackChanges) { - setUpgradeTrackChangesModal({ - show: true, - location: 'review-switcher', - }) - } else { - sendMB('editing-mode-change', { - role: permissionsLevel, - previousMode: mode, - newMode: 'review', - }) - if (user?.id) { - saveTrackChangesForCurrentUser(true) - } else { - saveTrackChanges({ on_for_guests: true }) - } - view.focus() - } - }} - description={ - permissionsLevel === 'review' && !trackedWrite - ? t('comment_only') - : t('edits_become_suggestions') - } - leadingIcon="rate_review" - active={trackedWrite && mode === 'review'} - > - {t('reviewing')} - - {showViewOption && ( - - {t('viewing')} - - )} +
diff --git a/services/web/frontend/js/features/review-panel/components/review-panel-container.tsx b/services/web/frontend/js/features/review-panel/components/review-panel-container.tsx index 4108a6ee0f..e5201e842e 100644 --- a/services/web/frontend/js/features/review-panel/components/review-panel-container.tsx +++ b/services/web/frontend/js/features/review-panel/components/review-panel-container.tsx @@ -5,11 +5,13 @@ import ReviewPanel from './review-panel' import ReviewModeSwitcher from './review-mode-switcher' import useReviewPanelLayout from '../hooks/use-review-panel-layout' import { useLayoutContext } from '@/shared/context/layout-context' +import { useFeatureFlag } from '@/shared/context/split-test-context' function ReviewPanelContainer() { const view = useCodeMirrorViewContext() const { showPanel, mini } = useReviewPanelLayout() const { focusMode } = useLayoutContext() + const isToolbarMigration = useFeatureFlag('writefull-toolbar-migration') if (!view) { return null @@ -17,7 +19,7 @@ function ReviewPanelContainer() { return ReactDOM.createPortal( <> - {!focusMode && } + {!focusMode && !isToolbarMigration && } {showPanel && } , view.scrollDOM diff --git a/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx b/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx index d7f3256dec..fcde83c988 100644 --- a/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx +++ b/services/web/frontend/js/features/source-editor/components/codemirror-toolbar.tsx @@ -18,6 +18,7 @@ import useDropdown from '../../../shared/hooks/use-dropdown' import { getPanel } from '@codemirror/view' import { createToolbarPanel } from '../extensions/toolbar/toolbar-panel' import EditorSwitch from './editor-switch' +import ReviewModeSwitcher from '@/features/review-panel/components/review-mode-switcher' import SwitchToPDFButton from './switch-to-pdf-button' import { DetacherSynctexControl } from '../../pdf-preview/components/detach-synctex-control' import DetachCompileButtonWrapper from '../../pdf-preview/components/detach-compile-button-wrapper' @@ -33,6 +34,7 @@ import Breadcrumbs from '@/features/source-editor/extensions/breadcrumbs' import classNames from 'classnames' import { useUserSettingsContext } from '@/shared/context/user-settings-context' import { useFeatureFlag } from '@/shared/context/split-test-context' +import { useProjectContext } from '@/shared/context/project-context' import importOverleafModules from '../../../../macros/import-overleaf-module.macro' import { useLayoutContext } from '@/shared/context/layout-context' import ReviewPanelHeaderBuffer from '@/features/review-panel/components/review-panel-header-buffer' @@ -69,6 +71,8 @@ const Toolbar = memo(function Toolbar() { userSettings: { breadcrumbs }, } = useUserSettingsContext() const visualPreviewEnabled = useFeatureFlag('visual-preview') + const isToolbarMigration = useFeatureFlag('writefull-toolbar-migration') + const { features } = useProjectContext() const { focusMode } = useLayoutContext() const [overflowed, setOverflowed] = useState(false) @@ -234,6 +238,13 @@ const Toolbar = memo(function Toolbar() {
{!visualPreviewEnabled && } + {/* trackChangesVisible controls provider/UI availability; trackChanges + (checked inside the switcher) controls the actual feature entitlement. + Users with trackChangesVisible:true but trackChanges:false see the + switcher and get an upgrade modal when clicking "Reviewing". */} + {isToolbarMigration && features.trackChangesVisible && ( + + )} {sourceEditorToolbarEndButtons.map( ({ import: { default: Component }, path }) => ( diff --git a/services/web/frontend/stylesheets/pages/editor/review-panel.scss b/services/web/frontend/stylesheets/pages/editor/review-panel.scss index 9d24f6a07e..c6d4068b35 100644 --- a/services/web/frontend/stylesheets/pages/editor/review-panel.scss +++ b/services/web/frontend/stylesheets/pages/editor/review-panel.scss @@ -11,6 +11,27 @@ $rp-type-blue: #6b7797; --review-panel-width: 230px; --review-panel-width-mini: 24px; --review-panel-color-scheme: dark; + --review-mode-switcher-reviewing-bg: var(--yellow-10); + --review-mode-switcher-reviewing-hover-bg: var(--yellow-20); + --review-mode-switcher-reviewing-color: var(--yellow-60); + + @include theme('default') { + .ide-redesign-main { + --review-panel-bg-color: var(--bg-dark-secondary); + --review-panel-color: var(--content-primary-dark); + --review-panel-color-secondary: var(--content-secondary-dark); + --review-panel-header-bg-color: var(--bg-dark-primary); + --review-panel-footer-bg-color: var(--bg-dark-primary); + --review-panel-entry-bg-color: var(--bg-dark-primary); + --review-panel-empty-state-bg-color: var(--bg-dark-primary); + --review-panel-button-hover-bg-color: var(--bg-dark-tertiary); + --review-panel-border-color: var(--border-divider-dark); + --review-panel-color-scheme: dark; + --review-mode-switcher-reviewing-bg: var(--yellow-70); + --review-mode-switcher-reviewing-hover-bg: var(--yellow-60); + --review-mode-switcher-reviewing-color: var(--yellow-10); + } + } } @include theme('light') { @@ -752,3 +773,53 @@ del.review-panel-content-highlight { } } } + +.ol-cm-toolbar { + .review-mode-switcher { + // In the toolbar the component is in normal flex flow — no absolute positioning. + position: static; + + &:hover, + &:focus-within { + .review-mode-switcher-toggle-button.editing { + background-color: var(--review-panel-button-hover-bg-color); + } + + .review-mode-switcher-toggle-button.reviewing { + background-color: var(--review-mode-switcher-reviewing-hover-bg); + } + + .review-mode-switcher-toggle-button.viewing { + background-color: var(--blue-20); + } + + // In the toolbar the label text does not expand on hover (would shift other buttons) + .review-mode-switcher-toggle-label { + display: none; + } + } + } + + .review-mode-switcher-toggle-button { + border-radius: var(--border-radius-base); + padding: var(--spacing-01) var(--spacing-02); + gap: var(--spacing-01); + height: auto; + cursor: pointer; + + &:focus-visible { + outline: 2px solid var(--content-primary); + outline-offset: 2px; + } + + &.editing { + background-color: var(--toolbar-alt-bg-color); + color: var(--toolbar-btn-color); + } + + &.reviewing { + background-color: var(--review-mode-switcher-reviewing-bg); + color: var(--review-mode-switcher-reviewing-color); + } + } +} diff --git a/services/web/frontend/stylesheets/pages/editor/toolbar.scss b/services/web/frontend/stylesheets/pages/editor/toolbar.scss index 780de74d4d..0743ff79ee 100644 --- a/services/web/frontend/stylesheets/pages/editor/toolbar.scss +++ b/services/web/frontend/stylesheets/pages/editor/toolbar.scss @@ -295,6 +295,12 @@ z-index: 10; // Prevent track changes showing over toolbar } +// Isolate secondary toolbar rows so they paint below the main toolbar (z-index: 10) +// and its dropdowns, which would otherwise be obscured due to DOM order. +.ol-cm-toolbar-wrapper > .writefull { + isolation: isolate; +} + /************************************** Toggle Switch ***************************************/