diff --git a/services/web/frontend/js/features/ide-redesign/hooks/use-switch-enable-new-editor-state.ts b/services/web/frontend/js/features/ide-redesign/hooks/use-switch-enable-new-editor-state.ts
new file mode 100644
index 0000000000..c32578591f
--- /dev/null
+++ b/services/web/frontend/js/features/ide-redesign/hooks/use-switch-enable-new-editor-state.ts
@@ -0,0 +1,34 @@
+import { postJSON } from '@/infrastructure/fetch-json'
+import { useUserSettingsContext } from '@/shared/context/user-settings-context'
+import { useCallback, useState } from 'react'
+
+export const useSwitchEnableNewEditorState = () => {
+ const [loading, setLoading] = useState(false)
+ const [error, setError] = useState('')
+ const { setUserSettings } = useUserSettingsContext()
+ const setEditorRedesignStatus = useCallback(
+ (status: boolean): Promise
=> {
+ setLoading(true)
+ setError('')
+ return new Promise((resolve, reject) => {
+ postJSON('/user/settings', { body: { enableNewEditor: status } })
+ .then(() => {
+ setUserSettings(current => ({
+ ...current,
+ enableNewEditor: status,
+ }))
+ resolve()
+ })
+ .catch(e => {
+ setError('Failed to update settings')
+ reject(e)
+ })
+ .finally(() => {
+ setLoading(false)
+ })
+ })
+ },
+ [setUserSettings]
+ )
+ return { loading, error, setEditorRedesignStatus }
+}
diff --git a/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
new file mode 100644
index 0000000000..5dbffab7d5
--- /dev/null
+++ b/services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
@@ -0,0 +1,13 @@
+import { useUserSettingsContext } from '@/shared/context/user-settings-context'
+import { isSplitTestEnabled } from '@/utils/splitTestUtils'
+
+// TODO: For now we're using the feature flag, but eventually we'll read this
+// from labs.
+export const canUseNewEditor = () => isSplitTestEnabled('editor-redesign')
+
+export const useIsNewEditorEnabled = () => {
+ const { userSettings } = useUserSettingsContext()
+ const hasAccess = canUseNewEditor()
+ const enabled = userSettings.enableNewEditor
+ return hasAccess && enabled
+}
diff --git a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
index a1d23c5e6f..8133ff31ee 100644
--- a/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
+++ b/services/web/frontend/js/features/pdf-preview/components/pdf-preview-pane.tsx
@@ -8,16 +8,16 @@ import { useDetachCompileContext as useCompileContext } from '../../../shared/co
import { PdfPreviewMessages } from './pdf-preview-messages'
import CompileTimeWarningUpgradePrompt from './compile-time-warning-upgrade-prompt'
import { PdfPreviewProvider } from './pdf-preview-provider'
-import { useFeatureFlag } from '@/shared/context/split-test-context'
import PdfPreviewHybridToolbarNew from '@/features/ide-redesign/components/pdf-preview/pdf-preview-hybrid-toolbar'
import PdfErrorState from '@/features/ide-redesign/components/pdf-preview/pdf-error-state'
+import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
function PdfPreviewPane() {
const { pdfUrl, hasShortCompileTimeout } = useCompileContext()
const classes = classNames('pdf', 'full-size', {
'pdf-empty': !pdfUrl,
})
- const newEditor = useFeatureFlag('editor-redesign')
+ const newEditor = useIsNewEditorEnabled()
return (
diff --git a/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx b/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx
index 916dd79f80..20cca2135b 100644
--- a/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx
+++ b/services/web/frontend/js/shared/components/menu-bar/menu-bar-option.tsx
@@ -1,17 +1,30 @@
import DropdownListItem from '@/features/ui/components/bootstrap-5/dropdown-list-item'
import { DropdownItem } from '@/features/ui/components/bootstrap-5/dropdown-menu'
import { useNestableDropdown } from '@/shared/hooks/use-nestable-dropdown'
+import { MouseEventHandler, ReactNode } from 'react'
type MenuBarOptionProps = {
title: string
- onClick?: () => void
+ onClick?: MouseEventHandler
+ disabled?: boolean
+ trailingIcon?: ReactNode
}
-export const MenuBarOption = ({ title, onClick }: MenuBarOptionProps) => {
+export const MenuBarOption = ({
+ title,
+ onClick,
+ disabled,
+ trailingIcon,
+}: MenuBarOptionProps) => {
const { setSelected } = useNestableDropdown()
return (
- setSelected(null)} onClick={onClick}>
+ setSelected(null)}
+ onClick={onClick}
+ disabled={disabled}
+ trailingIcon={trailingIcon}
+ >
{title}
diff --git a/services/web/frontend/js/shared/context/user-settings-context.tsx b/services/web/frontend/js/shared/context/user-settings-context.tsx
index 6a069f470e..b9b6f22ad1 100644
--- a/services/web/frontend/js/shared/context/user-settings-context.tsx
+++ b/services/web/frontend/js/shared/context/user-settings-context.tsx
@@ -27,6 +27,7 @@ const defaultSettings: UserSettings = {
lineHeight: 'normal',
mathPreview: true,
referencesSearchMode: 'advanced',
+ enableNewEditor: true,
}
type UserSettingsContextValue = {
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
index 87c34f04fa..9b3ec278dc 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/pages/all.scss
@@ -7,6 +7,7 @@
@import 'sidebar-v2-dash-pane';
@import 'editor/ide';
@import 'editor/ide-redesign';
+@import 'editor/ide-redesign-switcher-modal';
@import 'editor/rail';
@import 'editor/settings';
@import 'editor/toolbar';
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign-switcher-modal.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign-switcher-modal.scss
new file mode 100644
index 0000000000..ba2f2aa49d
--- /dev/null
+++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/ide-redesign-switcher-modal.scss
@@ -0,0 +1,24 @@
+.ide-redesign-switcher-modal .modal-content {
+ color: var(--content-primary);
+ font-size: var(--font-size-03);
+ line-height: var(--line-height-03);
+
+ p {
+ margin-bottom: 0;
+ }
+
+ .ide-redesign-switcher-modal-whats-new {
+ background-color: var(--bg-light-secondary);
+ border: 1px solid var(--border-divider);
+ padding: var(--spacing-05);
+ margin: var(--spacing-05) 0;
+ }
+
+ .ide-redesign-switcher-modal-leave-text {
+ color: var(--content-secondary);
+
+ a {
+ color: var(--link-ui);
+ }
+ }
+}
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar-redesign.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar-redesign.scss
index 9a9a7e271d..87185ab3ed 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar-redesign.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar-redesign.scss
@@ -8,6 +8,7 @@
--redesign-toolbar-logo-url: url('../../../../../public/img/ol-brand/overleaf-o-white.svg');
--redesign-subdued-button-color: var(--content-primary-dark);
--redesign-subdued-button-hover-background: var(--bg-dark-tertiary);
+ --redesign-toolbar-feedback-link-color: var(--link-ui-dark);
}
@include theme('light') {
@@ -18,6 +19,7 @@
--redesign-toolbar-logo-url: url('../../../../../public/img/ol-brand/overleaf-o-dark.svg');
--redesign-subdued-button-color: var(--content-primary);
--redesign-subdued-button-hover-background: var(--bg-light-tertiary);
+ --redesign-toolbar-feedback-link-color: var(--link-ui);
}
.ide-redesign-toolbar {
@@ -149,3 +151,22 @@
max-width: 400px;
margin: var(--spacing-02) 0;
}
+
+.ide-redesign-toolbar-labs-feedback-link {
+ &,
+ &:hover,
+ &:visited {
+ color: var(--redesign-toolbar-feedback-link-color);
+ }
+}
+
+.ide-redesign-labs-button.btn.btn-info {
+ @include ol-button-variant(
+ var(--content-positive),
+ var(--bg-accent-03),
+ var(--green-40),
+ var(--bg-accent-03),
+ var(--green-40),
+ false
+ );
+}
diff --git a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss
index 0e7aaaeca9..70b5dac91c 100644
--- a/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss
+++ b/services/web/frontend/stylesheets/bootstrap-5/pages/editor/toolbar.scss
@@ -94,7 +94,7 @@
.toolbar-right,
.toolbar-left {
- button:not(.back-to-editor-btn) {
+ button:not(.back-to-editor-btn, .toolbar-experiment-button) {
background: transparent;
box-shadow: none;
}
@@ -120,7 +120,7 @@
.toolbar-left > a:not(.btn),
.toolbar-left > button,
.toolbar-right > a:not(.btn),
- .toolbar-right > button:not(.back-to-editor-btn) {
+ .toolbar-right > button:not(.back-to-editor-btn, .toolbar-experiment-button) {
display: inline-block;
color: var(--toolbar-btn-color);
background-color: transparent;
@@ -502,3 +502,19 @@
.formatting-btn-icon:last-of-type {
border-right: 1px solid var(--formatting-btn-border);
}
+
+.toolbar-experiment-button.btn.btn-info {
+ @include ol-button-variant(
+ var(--content-positive),
+ var(--bg-accent-03),
+ var(--green-40),
+ var(--bg-accent-03),
+ var(--green-40),
+ false
+ );
+
+ max-height: 39px;
+ font-size: var(--font-size-01);
+ line-height: var(--line-height-01);
+ margin-right: var(--spacing-04);
+}
diff --git a/services/web/locales/en.json b/services/web/locales/en.json
index c293511896..dd743ad959 100644
--- a/services/web/locales/en.json
+++ b/services/web/locales/en.json
@@ -467,6 +467,7 @@
"customizing_figures": "Customizing figures",
"customizing_tables": "Customizing tables",
"da": "Danish",
+ "dark_mode": "Dark mode",
"date": "Date",
"date_and_owner": "Date and owner",
"de": "German",
@@ -916,6 +917,7 @@
"help_articles_matching": "Help articles matching your subject",
"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 survey0>.",
"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.",
+ "help_shape_the_future_of_overleaf": "Help shape the future of Overleaf",
"hide": "Hide",
"hide_configuration": "Hide configuration",
"hide_deleted_user": "Hide deleted users",
@@ -1118,6 +1120,7 @@
"ko": "Korean",
"labels_help_you_to_easily_reference_your_figures": "Labels help you to easily reference your figures throughout your document. To reference a figure within the text, reference the label using the <0>\\ref{...}0> command. This makes it easy to reference figures without needing to manually remember the figure numbering. <1>Learn more1>",
"labels_help_you_to_reference_your_tables": "Labels help you to reference your tables throughout your document easily. To reference a table within the text, reference the label using the <0>\\ref{...}0> command. This makes it easy to reference tables without manually remembering the table numbering. <1>Read about labels and cross-references1>.",
+ "labs": "Labs",
"labs_program_benefits": "By signing up for Overleaf Labs you can get your hands on in-development features and try them out as much as you like. All we ask in return is your honest feedback to help us develop and improve. It’s important to note that features available in this program are still being tested and actively developed. This means they could change, be removed, or become part of a premium plan.",
"language": "Language",
"language_suggestions_for_texts_in_any_language": ">Language suggestions/> for texts in any language",
@@ -1847,6 +1850,7 @@
"reverse_x_sort_order": "Reverse __x__ sort order",
"revert_pending_plan_change": "Revert scheduled plan change",
"review": "Review",
+ "review_panel_comments_and_track_changes": "Review panel – Comments & track changes",
"review_your_peers_work": "Review your peers’ work",
"reviewer": "Reviewer",
"reviewing": "Reviewing",
@@ -1973,6 +1977,7 @@
"set_up_single_sign_on": "Set up single sign-on (SSO)",
"set_up_sso": "Set up SSO",
"settings": "Settings",
+ "settings_for_git_github_and_dropbox_integrations": "Settings for Git, Github & Dropbox integrations",
"setup_another_account_under_a_personal_email_address": "Set up another Overleaf account under a personal email address.",
"share": "Share",
"share_project": "Share Project",
@@ -2140,6 +2145,8 @@
"switch_back_to_monthly_pay_20_more": "Switch back to monthly (20% more)",
"switch_plan": "Switch plan",
"switch_to_editor": "Switch to editor",
+ "switch_to_new_editor": "Switch to new editor",
+ "switch_to_old_editor": "Switch to old editor",
"switch_to_pdf": "Switch to PDF",
"switch_to_standard_plan": "Switch to Standard plan",
"symbol_palette": "Symbol palette",
@@ -2195,6 +2202,7 @@
"thank_you_for_being_part_of_our_beta_program": "Thank you for being part of our Beta Program, where you can have <0>early access to new features0> and help us understand your needs better",
"thank_you_for_your_feedback": "Thank you for your feedback!",
"thanks": "Thanks",
+ "thanks_for_being_part_of_this_labs_experiment_your_feedback_will_help_us_make_the_new_editor_the_best_yet": "Thanks for being part of this Labs experiment. Your feedback and support will help us make the new editor the best yet! Some features are still in progress, so if you need something that’s missing, you can switch back to the old editor.",
"thanks_for_confirming_your_email_address": "Thanks for confirming your email address",
"thanks_for_getting_in_touch": "Thanks for getting in touch. Our team will get back to you by email as soon as possible.",
"thanks_for_subscribing": "Thanks for subscribing!",
@@ -2209,6 +2217,7 @@
"the_following_files_and_folders_already_exist_in_this_project": "The following files and folders already exist in this project:",
"the_following_folder_already_exists_in_this_project": "The following folder already exists in this project:",
"the_following_folder_already_exists_in_this_project_plural": "The following folders already exist in this project:",
+ "the_new_overleaf_editor": "The new Overleaf editor",
"the_next_payment_will_be_collected_on": "The next payment will be collected on __date__.",
"the_original_text_has_changed": "The original text has changed, so this suggestion can’t be applied",
"the_project_that_contains_this_file_is_not_shared_with_you": "The project that contains this file is not shared with you",
@@ -2236,6 +2245,7 @@
"this_experiment_isnt_accepting_new_participants": "This experiment isn’t accepting new participants.",
"this_field_is_required": "This field is required",
"this_grants_access_to_features_2": "This grants you access to <0>__appName__0> <0>__featureType__0> features.",
+ "this_is_a_labs_experiment_for_the_new_overleaf_editor_some_features_are_still_in_progress": "This is a Labs experiment for the new Overleaf editor. Some features are still in progress, so if you need something that’s missing, you can switch back to the old editor at any time.",
"this_is_a_new_feature": "This is a new feature",
"this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "This is the file that references pulled from your reference manager will be added to.",
"this_is_your_template": "This is your template from your project",
@@ -2371,6 +2381,7 @@
"try_premium_for_free": "Try Premium for free",
"try_recompile_project_or_troubleshoot": "Please try recompiling the project from scratch, and if that doesn’t help, follow our <0>troubleshooting guide0>.",
"try_relinking_provider": "It looks like you need to re-link your __provider__ account.",
+ "try_the_new_editor": "Try the new editor",
"try_to_compile_despite_errors": "Try to compile despite errors",
"turn_off": "Turn off",
"turn_off_link_sharing": "Turn off link sharing",
@@ -2539,6 +2550,7 @@
"well_be_here_when_youre_ready": "We’ll be here when you’re ready to dive back in! 🦆",
"were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "We’re making some <0>changes to project sharing0>. This means, as someone with edit access, your name and email address will be visible to the project owner and other editors.",
"were_performing_maintenance": "We’re performing maintenance on Overleaf and you need to wait a moment. Sorry for any inconvenience. The editor will refresh automatically in __seconds__ seconds.",
+ "were_redesigning_our_editor_to_make_it_easier_to_use": "We’re redesigning our editor to make it easier to use and ensure it’s future ready. Try it out and give us your feedback to help us get this right. (Some features are still in the works, so you can switch back at any time.)",
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_this_project": "We’ve recently <0>reduced the compile timeout limit0> on our free plan, which may have affected this project.",
"weve_recently_reduced_the_compile_timeout_limit_which_may_have_affected_your_project": "We’ve recently <0>reduced the compile timeout limit0> on our free plan, which may have affected your project.",
"what_do_you_need": "What do you need?",
@@ -2547,6 +2559,8 @@
"what_does_this_mean_for_you": "This means:",
"what_happens_when_sso_is_enabled": "What happens when SSO is enabled?",
"what_should_we_call_you": "What should we call you?",
+ "whats_new": "What’s new?",
+ "whats_next": "What’s next?",
"when_you_join_labs": "When you join Labs, you can choose which experiments you want to be part of. Once you’ve done that, you can use Overleaf as normal, but you’ll see any labs features marked with this badge:",
"when_you_tick_the_include_caption_box": "When you tick the box “Include caption” the image will be inserted into your document with a placeholder caption. To edit it, you simply select the placeholder text and type to replace it with your own.",
"why_latex": "Why LaTeX?",
@@ -2595,6 +2609,7 @@
"you_are_on_x_plan_as_member_of_group_subscription_y_administered_by_z": "You are on our <0>__planName__0> plan as a <1>member1> of the group subscription <1>__groupName__1> administered by <1>__adminEmail__1>",
"you_can_also_choose_to_view_anonymously_or_leave_the_project": "You can also choose to <0>view anonymously0> (you will lose edit access) or <1>leave the project1>.",
"you_can_buy_this_plan_but_not_as_a_trial": "You can buy this plan but not as a trial, as you’ve completed a trial recently.",
+ "you_can_leave_the_experiment_from_your_account_settings_at_any_time": "You can leave the experiment from your <0>account settings0> at any time.",
"you_can_manage_your_reference_manager_integrations_from_your_account_settings_page": "You can manage your reference manager integrations from your <0>account settings page0>.",
"you_can_now_enable_sso": "You can now enable SSO on your Group settings page.",
"you_can_now_log_in_sso": "You can now log in through your institution and if eligible you will receive <0>__appName__ Professional features0>.",
@@ -2672,6 +2687,7 @@
"youre_about_to_enable_single_sign_on_sso_only": "You’re about to enable single sign-on (SSO). Before you do this, you should ensure you’re confident the SSO configuration is correct.",
"youre_adding_x_users_to_your_plan_giving_you_a_total_of_y_users": "You’re adding <0>__adding__0> users to your plan giving you a total of <1>__total__1> users.",
"youre_already_setup_for_sso": "You’re already set up for SSO",
+ "youre_helping_us_shape_the_future_of_overleaf": "You’re helping us shape the future of Overleaf",
"youre_joining": "You’re joining",
"youre_on_free_trial_which_ends_on": "You’re on a free trial which ends on <0>__date__0>.",
"youre_signed_in_as_logout": "You’re signed in as <0>__email__0>. <1>Log out.1>",
diff --git a/services/web/test/unit/src/User/UserControllerTests.js b/services/web/test/unit/src/User/UserControllerTests.js
index 98ae8db73e..8dc8f034b7 100644
--- a/services/web/test/unit/src/User/UserControllerTests.js
+++ b/services/web/test/unit/src/User/UserControllerTests.js
@@ -452,6 +452,33 @@ describe('UserController', function () {
this.UserController.updateUserSettings(this.req, this.res)
})
+ it('should set enableNewEditor to true', function (done) {
+ this.req.body = { enableNewEditor: true }
+ this.res.sendStatus = code => {
+ this.user.ace.enableNewEditor.should.equal(true)
+ done()
+ }
+ this.UserController.updateUserSettings(this.req, this.res)
+ })
+
+ it('should set enableNewEditor to false', function (done) {
+ this.req.body = { enableNewEditor: false }
+ this.res.sendStatus = code => {
+ this.user.ace.enableNewEditor.should.equal(false)
+ done()
+ }
+ this.UserController.updateUserSettings(this.req, this.res)
+ })
+
+ it('should keep enableNewEditor a boolean', function (done) {
+ this.req.body = { enableNewEditor: 'foobar' }
+ this.res.sendStatus = code => {
+ this.user.ace.enableNewEditor.should.equal(true)
+ done()
+ }
+ this.UserController.updateUserSettings(this.req, this.res)
+ })
+
it('should send an error if the email is 0 len', function (done) {
this.req.body.email = ''
this.res.sendStatus = function (code) {
diff --git a/services/web/types/user-settings.ts b/services/web/types/user-settings.ts
index 2753e09777..3e748d937e 100644
--- a/services/web/types/user-settings.ts
+++ b/services/web/types/user-settings.ts
@@ -16,4 +16,5 @@ export type UserSettings = {
lineHeight: LineHeight
mathPreview: boolean
referencesSearchMode: 'advanced' | 'simple'
+ enableNewEditor: boolean
}