Merge pull request #29607 from overleaf/dp-redesign-opt-in
Add opt-in for editor redesign GitOrigin-RevId: 29ec8f4045a6bf29ab26a5ce5bceff70fb3aba6e
This commit is contained in:
@@ -864,6 +864,12 @@ const _ProjectController = {
|
||||
!userHasPremiumSub &&
|
||||
!userInNonIndividualSub
|
||||
|
||||
const userSettings = await UserSettingsHelper.buildUserSettings(
|
||||
req,
|
||||
res,
|
||||
user
|
||||
)
|
||||
|
||||
res.render(template, {
|
||||
title: project.name,
|
||||
priority_title: true,
|
||||
@@ -902,7 +908,7 @@ const _ProjectController = {
|
||||
isMemberOfGroupSubscription: userIsMemberOfGroupSubscription,
|
||||
hasInstitutionLicence: userHasInstitutionLicence,
|
||||
},
|
||||
userSettings: UserSettingsHelper.buildUserSettings(user),
|
||||
userSettings,
|
||||
labsExperiments: user.labsExperiments ?? [],
|
||||
privilegeLevel,
|
||||
anonymous,
|
||||
|
||||
@@ -542,6 +542,12 @@ async function projectListPage(req, res, next) {
|
||||
'themed-project-dashboard'
|
||||
)
|
||||
|
||||
const userSettings = await UserSettingsHelper.buildUserSettings(
|
||||
req,
|
||||
res,
|
||||
user
|
||||
)
|
||||
|
||||
res.render('project/list-react', {
|
||||
title: 'your_projects',
|
||||
usersBestSubscription,
|
||||
@@ -550,7 +556,7 @@ async function projectListPage(req, res, next) {
|
||||
user,
|
||||
userAffiliations,
|
||||
userEmails,
|
||||
userSettings: UserSettingsHelper.buildUserSettings(user),
|
||||
userSettings,
|
||||
reconfirmedViaSAML,
|
||||
allInReconfirmNotificationPeriods,
|
||||
survey,
|
||||
|
||||
@@ -1,4 +1,38 @@
|
||||
function buildUserSettings(user) {
|
||||
import SplitTestHandler from '../SplitTests/SplitTestHandler.mjs'
|
||||
|
||||
// Copied from services/web/frontend/js/features/ide-redesign/utils/new-editor-utils.ts
|
||||
const SPLIT_TEST_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 8, 23, 13, 0, 0)) // 2pm British Summer Time on September 23, 2025
|
||||
const NEW_USER_CUTOFF_DATE = new Date(Date.UTC(2025, 10, 12, 12, 0, 0)) // 12pm GMT on November 12, 2025
|
||||
|
||||
async function getEnableNewEditorDefault(req, res, user) {
|
||||
if (req.query['existing-user-override'] === 'true') {
|
||||
return false
|
||||
}
|
||||
|
||||
if (req.query['skip-new-user-check'] === 'true') {
|
||||
return true
|
||||
}
|
||||
|
||||
if (user.signUpDate >= NEW_USER_CUTOFF_DATE) {
|
||||
return true
|
||||
}
|
||||
|
||||
if (user.signUpDate >= SPLIT_TEST_USER_CUTOFF_DATE) {
|
||||
const assignment = await SplitTestHandler.promises.getAssignment(
|
||||
req,
|
||||
res,
|
||||
'editor-redesign-new-users'
|
||||
)
|
||||
|
||||
return assignment.variant !== 'default'
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
async function buildUserSettings(req, res, user) {
|
||||
const defaultEnableNewEditor = await getEnableNewEditorDefault(req, res, user)
|
||||
|
||||
return {
|
||||
mode: user.ace.mode,
|
||||
editorTheme: user.ace.theme,
|
||||
@@ -13,7 +47,7 @@ function buildUserSettings(user) {
|
||||
mathPreview: user.ace.mathPreview,
|
||||
breadcrumbs: user.ace.breadcrumbs,
|
||||
referencesSearchMode: user.ace.referencesSearchMode,
|
||||
enableNewEditor: user.ace.enableNewEditor ?? true,
|
||||
enableNewEditor: user.ace.enableNewEditor ?? defaultEnableNewEditor,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@ const VALID_KEYS = [
|
||||
'rolling-compile-image-changed',
|
||||
'groups-enterprise-banner',
|
||||
'groups-enterprise-banner-repeat',
|
||||
'new-editor-opt-in',
|
||||
'new-editor-intro',
|
||||
]
|
||||
|
||||
async function completeTutorial(req, res, next) {
|
||||
|
||||
@@ -182,12 +182,11 @@
|
||||
"back_to_subscription": "",
|
||||
"back_to_your_projects": "",
|
||||
"basic_compile_time": "",
|
||||
"be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor": "",
|
||||
"before_you_use_error_assistant": "",
|
||||
"beta": "",
|
||||
"beta_program_already_participating": "",
|
||||
"beta_program_benefits": "",
|
||||
"beta_program_not_participating": "",
|
||||
"beta_program_the_new_overleaf_editor": "",
|
||||
"billed_annually_at": "",
|
||||
"billed_monthly_at": "",
|
||||
"billed_yearly": "",
|
||||
@@ -240,6 +239,7 @@
|
||||
"center": "",
|
||||
"change": "",
|
||||
"change_currency": "",
|
||||
"change_how_you_see_the_editor": "",
|
||||
"change_language": "",
|
||||
"change_or_cancel-cancel": "",
|
||||
"change_or_cancel-change": "",
|
||||
@@ -581,6 +581,7 @@
|
||||
"expires": "",
|
||||
"expires_in_days": "",
|
||||
"expires_on": "",
|
||||
"explore_what_s_new": "",
|
||||
"export_csv": "",
|
||||
"export_project_to_github": "",
|
||||
"failed_to_send_group_invite_to_email": "",
|
||||
@@ -615,11 +616,13 @@
|
||||
"files_selected": "",
|
||||
"filter_projects": "",
|
||||
"find": "",
|
||||
"find_and_fix_errors_faster": "",
|
||||
"find_out_more": "",
|
||||
"find_out_more_about_institution_login": "",
|
||||
"find_out_more_about_the_file_outline": "",
|
||||
"find_out_more_nt": "",
|
||||
"finding_a_fix": "",
|
||||
"finish": "",
|
||||
"first_name": "",
|
||||
"fit_to_height": "",
|
||||
"fit_to_width": "",
|
||||
@@ -848,7 +851,6 @@
|
||||
"imported_from_zotero_at_date": "",
|
||||
"importing": "",
|
||||
"importing_and_merging_changes_in_github": "",
|
||||
"improved_dark_mode": "",
|
||||
"in_order_to_match_institutional_metadata_2": "",
|
||||
"in_order_to_match_institutional_metadata_associated": "",
|
||||
"include_caption": "",
|
||||
@@ -887,6 +889,7 @@
|
||||
"integrations": "",
|
||||
"integrations_like_github": "",
|
||||
"interested_in_cheaper_personal_plan": "",
|
||||
"introducing_overleafs_new_look": "",
|
||||
"invalid_confirmation_code": "",
|
||||
"invalid_email": "",
|
||||
"invalid_file_name": "",
|
||||
@@ -1112,15 +1115,11 @@
|
||||
"new_compile_domain_notice": "",
|
||||
"new_compiles_in_this_project_will_automatically_use_the_newest_version": "",
|
||||
"new_create_tables_and_equations": "",
|
||||
"new_editor": "",
|
||||
"new_editor_experience": "",
|
||||
"new_editor_info": "",
|
||||
"new_editor_look": "",
|
||||
"new_error_logs_make_it_easier_to_find_whats_wrong": "",
|
||||
"new_file": "",
|
||||
"new_folder": "",
|
||||
"new_look_and_feel": "",
|
||||
"new_look_and_placement_of_the_settings": "",
|
||||
"new_name": "",
|
||||
"new_navigation_introducing_left_hand_side_rail_and_top_menus": "",
|
||||
"new_password": "",
|
||||
"new_project": "",
|
||||
"new_subscription_will_be_billed_immediately": "",
|
||||
@@ -1165,6 +1164,7 @@
|
||||
"not_a_student": "",
|
||||
"not_managed": "",
|
||||
"not_now": "",
|
||||
"not_sure_about_switching_yet": "",
|
||||
"notification": "",
|
||||
"notification_personal_and_group_subscriptions": "",
|
||||
"notification_project_invite_accepted_message": "",
|
||||
@@ -1217,6 +1217,7 @@
|
||||
"overleaf_labs": "",
|
||||
"overleaf_logo": "",
|
||||
"overleafs_functionality_meets_my_needs": "",
|
||||
"overleafs_new_look_is_here": "",
|
||||
"overview": "",
|
||||
"overwrite": "",
|
||||
"overwriting_the_original_folder": "",
|
||||
@@ -1397,6 +1398,7 @@
|
||||
"read_lines_from_path": "",
|
||||
"read_more": "",
|
||||
"read_more_about_managed_users": "",
|
||||
"read_more_about_the_new_editor": "",
|
||||
"read_only_dropbox_sync_message": "",
|
||||
"read_only_token": "",
|
||||
"read_write_token": "",
|
||||
@@ -1504,7 +1506,6 @@
|
||||
"revert_pending_plan_change": "",
|
||||
"review": "",
|
||||
"review_panel": "",
|
||||
"review_panel_and_error_logs_moved_to_the_left": "",
|
||||
"reviewer": "",
|
||||
"reviewer_dropbox_sync_message": "",
|
||||
"reviewing": "",
|
||||
@@ -1621,6 +1622,7 @@
|
||||
"setup_another_account_under_a_personal_email_address": "",
|
||||
"share": "",
|
||||
"share_feedback": "",
|
||||
"share_feedback_on_the_new_editor": "",
|
||||
"share_project": "",
|
||||
"shared_with_you": "",
|
||||
"sharelatex_beta_program": "",
|
||||
@@ -1648,6 +1650,7 @@
|
||||
"showing_x_results_of_total": "",
|
||||
"sign_up": "",
|
||||
"simple_search_mode": "",
|
||||
"simplified_working_starts_here": "",
|
||||
"single_sign_on_sso": "",
|
||||
"size": "",
|
||||
"something_not_right": "",
|
||||
@@ -1775,10 +1778,10 @@
|
||||
"sure_you_want_to_change_plan": "",
|
||||
"sure_you_want_to_delete": "",
|
||||
"sure_you_want_to_leave_group": "",
|
||||
"switch_between_dark_and_light_mode": "",
|
||||
"switch_compile_mode_for_faster_draft_compilation": "",
|
||||
"switch_easily_between_your_files_comments_track_changes_and_more": "",
|
||||
"switch_to_editor": "",
|
||||
"switch_to_new_editor": "",
|
||||
"switch_to_old_editor": "",
|
||||
"switch_to_pdf": "",
|
||||
"switch_to_personal_email_to_keep_your_accounts_separate": "",
|
||||
"switch_to_standard_plan": "",
|
||||
@@ -1836,7 +1839,7 @@
|
||||
"the_following_folder_already_exists_in_this_project": "",
|
||||
"the_following_folder_already_exists_in_this_project_plural": "",
|
||||
"the_latex_engine_used_for_compiling": "",
|
||||
"the_new_overleaf_editor_try_now_in_beta": "",
|
||||
"the_new_overleaf_editor_info": "",
|
||||
"the_next_payment_will_be_collected_on": "",
|
||||
"the_original_text_has_changed": "",
|
||||
"the_overleaf_color_scheme": "",
|
||||
@@ -1867,7 +1870,6 @@
|
||||
"this_experiment_isnt_accepting_new_participants": "",
|
||||
"this_field_is_required": "",
|
||||
"this_grants_access_to_features_2": "",
|
||||
"this_is_a_beta_release_for_the_new_overleaf_editor": "",
|
||||
"this_is_a_new_feature": "",
|
||||
"this_is_the_file_that_references_pulled_from_your_reference_manager_will_be_added_to": "",
|
||||
"this_organization_is_tax_exempt": "",
|
||||
@@ -1996,10 +1998,12 @@
|
||||
"try_for_free": "",
|
||||
"try_it_for_free": "",
|
||||
"try_now": "",
|
||||
"try_out_the_new_editor_now": "",
|
||||
"try_premium_for_free": "",
|
||||
"try_recompile_project_or_troubleshoot": "",
|
||||
"try_relinking_provider": "",
|
||||
"try_the_new_editor": "",
|
||||
"try_the_new_editor_design": "",
|
||||
"try_the_new_look": "",
|
||||
"try_to_compile_despite_errors": "",
|
||||
"turn_off": "",
|
||||
"turn_off_link_sharing": "",
|
||||
@@ -2159,13 +2163,12 @@
|
||||
"well_be_here_when_youre_ready": "",
|
||||
"were_making_some_changes_to_project_sharing_this_means_you_will_be_visible": "",
|
||||
"were_performing_maintenance": "",
|
||||
"weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready": "",
|
||||
"weve_made_it_easier_to_find_and_use_the_tools_you_need_today": "",
|
||||
"what_did_you_find_most_helpful": "",
|
||||
"what_do_you_need_help_with": "",
|
||||
"what_does_this_mean_for_you": "",
|
||||
"what_happens_when_sso_is_enabled": "",
|
||||
"what_should_we_call_you": "",
|
||||
"whats_different": "",
|
||||
"when_you_tick_the_include_caption_box": "",
|
||||
"why_not_pause_instead": "",
|
||||
"wide": "",
|
||||
@@ -2215,7 +2218,6 @@
|
||||
"you_can_select_or_invite_collaborator": "",
|
||||
"you_can_select_or_invite_collaborator_plural": "",
|
||||
"you_can_still_use_your_premium_features": "",
|
||||
"you_can_switch_back_to_the_old_editor_at_any_time": "",
|
||||
"you_cant_add_or_change_password_due_to_sso": "",
|
||||
"you_cant_join_this_group_subscription": "",
|
||||
"you_dont_have_any_add_ons_on_your_account": "",
|
||||
|
||||
+9
-4
@@ -4,22 +4,27 @@ import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
import { useCallback } from 'react'
|
||||
import {
|
||||
canUseNewEditorAsNewUser,
|
||||
canUseNewEditor,
|
||||
useIsNewEditorEnabled,
|
||||
} from '@/features/ide-redesign/utils/new-editor-utils'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
|
||||
export default function SettingsNewEditor() {
|
||||
const { t } = useTranslation()
|
||||
const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const enabled = useIsNewEditorEnabled()
|
||||
const show = canUseNewEditorAsNewUser()
|
||||
const show = canUseNewEditor()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
const onChange = useCallback(
|
||||
(newValue: boolean) => {
|
||||
sendEvent('switch-to-new-editor', {
|
||||
location: 'left-menu',
|
||||
})
|
||||
setEditorRedesignStatus(newValue).then(() => setLeftMenuShown(false))
|
||||
},
|
||||
[setEditorRedesignStatus, setLeftMenuShown]
|
||||
[setEditorRedesignStatus, setLeftMenuShown, sendEvent]
|
||||
)
|
||||
|
||||
if (!show) {
|
||||
@@ -40,7 +45,7 @@ export default function SettingsNewEditor() {
|
||||
label: t('off'),
|
||||
},
|
||||
]}
|
||||
label={t('new_editor')}
|
||||
label={t('new_editor_look')}
|
||||
name="new-editor-setting"
|
||||
/>
|
||||
)
|
||||
|
||||
+11
-12
@@ -1,34 +1,33 @@
|
||||
import { useCallback } from 'react'
|
||||
import OLButton from '../../shared/components/ol/ol-button'
|
||||
import { useIdeRedesignSwitcherContext } from '../ide-react/context/ide-redesign-switcher-context'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { canUseNewEditorAsExistingUser } from '../ide-redesign/utils/new-editor-utils'
|
||||
import { useSwitchEnableNewEditorState } from '../ide-redesign/hooks/use-switch-enable-new-editor-state'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
|
||||
const TryNewEditorButton = () => {
|
||||
const { t } = useTranslation()
|
||||
const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
|
||||
const showModal = canUseNewEditorAsExistingUser()
|
||||
const { loading, setEditorRedesignStatus } = useSwitchEnableNewEditorState()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
const onClick = useCallback(() => {
|
||||
if (showModal) {
|
||||
setShowSwitcherModal(true)
|
||||
} else {
|
||||
setEditorRedesignStatus(true)
|
||||
}
|
||||
}, [setShowSwitcherModal, showModal, setEditorRedesignStatus])
|
||||
sendEvent('switch-to-new-editor', {
|
||||
location: 'toolbar',
|
||||
})
|
||||
setEditorRedesignStatus(true)
|
||||
}, [setEditorRedesignStatus, sendEvent])
|
||||
|
||||
return (
|
||||
<div className="d-flex align-items-center">
|
||||
<OLButton
|
||||
className="toolbar-experiment-button"
|
||||
className="toolbar-experiment-button try-new-editor-button"
|
||||
onClick={onClick}
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
isLoading={loading}
|
||||
>
|
||||
{t('try_the_new_editor')}
|
||||
<MaterialIcon type="fiber_new" />
|
||||
{t('try_the_new_editor_design')}
|
||||
</OLButton>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -2,10 +2,8 @@ import { memo } from 'react'
|
||||
import ForceDisconnected from '@/features/ide-react/components/modals/force-disconnected'
|
||||
import { UnsavedDocs } from '@/features/ide-react/components/unsaved-docs/unsaved-docs'
|
||||
import SystemMessages from '@/shared/components/system-messages'
|
||||
import {
|
||||
IdeRedesignSwitcherModal,
|
||||
IdeRedesignIntroModal,
|
||||
} from '@/features/ide-redesign/components/switcher-modal/beta-modal'
|
||||
import NewEditorPromoModal from '@/features/ide-redesign/components/new-editor-promo-modal'
|
||||
import NewEditorIntroModal from '@/features/ide-redesign/components/new-editor-intro-modal'
|
||||
|
||||
export const Modals = memo(() => {
|
||||
return (
|
||||
@@ -13,8 +11,8 @@ export const Modals = memo(() => {
|
||||
<ForceDisconnected />
|
||||
<UnsavedDocs />
|
||||
<SystemMessages />
|
||||
<IdeRedesignIntroModal />
|
||||
<IdeRedesignSwitcherModal />
|
||||
<NewEditorPromoModal />
|
||||
<NewEditorIntroModal />
|
||||
</>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
import {
|
||||
createContext,
|
||||
Dispatch,
|
||||
FC,
|
||||
SetStateAction,
|
||||
useContext,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
type IdeRedesignSwitcherContextValue = {
|
||||
showSwitcherModal: boolean
|
||||
setShowSwitcherModal: Dispatch<SetStateAction<boolean>>
|
||||
}
|
||||
|
||||
export const IdeRedesignSwitcherContext = createContext<
|
||||
IdeRedesignSwitcherContextValue | undefined
|
||||
>(undefined)
|
||||
|
||||
export const IdeRedesignSwitcherProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [showSwitcherModal, setShowSwitcherModal] = useState(false)
|
||||
|
||||
return (
|
||||
<IdeRedesignSwitcherContext.Provider
|
||||
value={{ showSwitcherModal, setShowSwitcherModal }}
|
||||
>
|
||||
{children}
|
||||
</IdeRedesignSwitcherContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useIdeRedesignSwitcherContext = () => {
|
||||
const context = useContext(IdeRedesignSwitcherContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useIdeRedesignSwitcherContext is only available inside IdeRedesignSwitcherProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -28,8 +28,8 @@ import { SplitTestProvider } from '@/shared/context/split-test-context'
|
||||
import { UserProvider } from '@/shared/context/user-context'
|
||||
import { UserFeaturesProvider } from '@/shared/context/user-features-context'
|
||||
import { UserSettingsProvider } from '@/shared/context/user-settings-context'
|
||||
import { IdeRedesignSwitcherProvider } from './ide-redesign-switcher-context'
|
||||
import { CommandRegistryProvider } from './command-registry-context'
|
||||
import { NewEditorTourProvider } from '@/features/ide-redesign/contexts/new-editor-tour-context'
|
||||
import { EditorSelectionProvider } from '@/shared/context/editor-selection-context'
|
||||
import importOverleafModules from '../../../../macros/import-overleaf-module.macro'
|
||||
|
||||
@@ -72,9 +72,9 @@ export const ReactContextRoot: FC<
|
||||
SplitTestProvider,
|
||||
UserProvider,
|
||||
UserSettingsProvider,
|
||||
IdeRedesignSwitcherProvider,
|
||||
CommandRegistryProvider,
|
||||
UserFeaturesProvider,
|
||||
NewEditorTourProvider,
|
||||
EditorSelectionProvider,
|
||||
...providers,
|
||||
}
|
||||
@@ -111,17 +111,17 @@ export const ReactContextRoot: FC<
|
||||
<Providers.PermissionsProvider>
|
||||
<Providers.RailProvider>
|
||||
<Providers.LayoutProvider>
|
||||
<Providers.ProjectSettingsProvider>
|
||||
<Providers.EditorManagerProvider>
|
||||
<Providers.ReferencesProvider>
|
||||
<Providers.LocalCompileProvider>
|
||||
<Providers.DetachCompileProvider>
|
||||
<Providers.ChatProvider>
|
||||
<Providers.FileTreeOpenProvider>
|
||||
<Providers.OnlineUsersProvider>
|
||||
<Providers.MetadataProvider>
|
||||
<Providers.OutlineProvider>
|
||||
<Providers.IdeRedesignSwitcherProvider>
|
||||
<Providers.NewEditorTourProvider>
|
||||
<Providers.ProjectSettingsProvider>
|
||||
<Providers.EditorManagerProvider>
|
||||
<Providers.ReferencesProvider>
|
||||
<Providers.LocalCompileProvider>
|
||||
<Providers.DetachCompileProvider>
|
||||
<Providers.ChatProvider>
|
||||
<Providers.FileTreeOpenProvider>
|
||||
<Providers.OnlineUsersProvider>
|
||||
<Providers.MetadataProvider>
|
||||
<Providers.OutlineProvider>
|
||||
<Providers.CommandRegistryProvider>
|
||||
<Providers.EditorSelectionProvider>
|
||||
{
|
||||
@@ -129,17 +129,17 @@ export const ReactContextRoot: FC<
|
||||
}
|
||||
</Providers.EditorSelectionProvider>
|
||||
</Providers.CommandRegistryProvider>
|
||||
</Providers.IdeRedesignSwitcherProvider>
|
||||
</Providers.OutlineProvider>
|
||||
</Providers.MetadataProvider>
|
||||
</Providers.OnlineUsersProvider>
|
||||
</Providers.FileTreeOpenProvider>
|
||||
</Providers.ChatProvider>
|
||||
</Providers.DetachCompileProvider>
|
||||
</Providers.LocalCompileProvider>
|
||||
</Providers.ReferencesProvider>
|
||||
</Providers.EditorManagerProvider>
|
||||
</Providers.ProjectSettingsProvider>
|
||||
</Providers.OutlineProvider>
|
||||
</Providers.MetadataProvider>
|
||||
</Providers.OnlineUsersProvider>
|
||||
</Providers.FileTreeOpenProvider>
|
||||
</Providers.ChatProvider>
|
||||
</Providers.DetachCompileProvider>
|
||||
</Providers.LocalCompileProvider>
|
||||
</Providers.ReferencesProvider>
|
||||
</Providers.EditorManagerProvider>
|
||||
</Providers.ProjectSettingsProvider>
|
||||
</Providers.NewEditorTourProvider>
|
||||
</Providers.LayoutProvider>
|
||||
</Providers.RailProvider>
|
||||
</Providers.PermissionsProvider>
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import EditorTourTooltip from './editor-tour-tooltip'
|
||||
|
||||
export default function EditorTourLogsTooltip({
|
||||
target,
|
||||
}: {
|
||||
target: HTMLElement | null
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<EditorTourTooltip
|
||||
target={target}
|
||||
placement="bottom"
|
||||
stage="logs"
|
||||
header={t('find_and_fix_errors_faster')}
|
||||
>
|
||||
{t('new_error_logs_make_it_easier_to_find_whats_wrong')}
|
||||
</EditorTourTooltip>
|
||||
)
|
||||
}
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import EditorTourTooltip from './editor-tour-tooltip'
|
||||
|
||||
export default function EditorTourRailTooltip({
|
||||
target,
|
||||
}: {
|
||||
target: HTMLElement | null
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<EditorTourTooltip
|
||||
target={target}
|
||||
placement="right-start"
|
||||
stage="rail"
|
||||
header={t('simplified_working_starts_here')}
|
||||
>
|
||||
{t('switch_easily_between_your_files_comments_track_changes_and_more')}
|
||||
</EditorTourTooltip>
|
||||
)
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import EditorTourTooltip from './editor-tour-tooltip'
|
||||
|
||||
export default function EditorTourSwitchBackTooltip({
|
||||
target,
|
||||
}: {
|
||||
target: HTMLElement | null
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<EditorTourTooltip
|
||||
target={target}
|
||||
placement="right-end"
|
||||
stage="switch-back"
|
||||
header={t('not_sure_about_switching_yet')}
|
||||
>
|
||||
<Trans
|
||||
i18nKey="read_more_about_the_new_editor"
|
||||
components={[
|
||||
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
||||
<a
|
||||
href="https://www.overleaf.com/blog/introducing-overleafs-new-look"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="link"
|
||||
/>,
|
||||
<strong key="strong" />,
|
||||
]}
|
||||
/>
|
||||
</EditorTourTooltip>
|
||||
)
|
||||
}
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import EditorTourTooltip from './editor-tour-tooltip'
|
||||
|
||||
export default function EditorTourThemeTooltip({
|
||||
target,
|
||||
}: {
|
||||
target: HTMLElement | null
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<EditorTourTooltip
|
||||
target={target}
|
||||
placement="right-end"
|
||||
stage="theme"
|
||||
header={t('switch_between_dark_and_light_mode')}
|
||||
>
|
||||
<Trans
|
||||
i18nKey="change_how_you_see_the_editor"
|
||||
components={{ strong: <strong /> }}
|
||||
/>
|
||||
</EditorTourTooltip>
|
||||
)
|
||||
}
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
import { Overlay, OverlayProps, Popover } from 'react-bootstrap'
|
||||
import {
|
||||
NewEditorTourStage,
|
||||
useNewEditorTourContext,
|
||||
} from '../../contexts/new-editor-tour-context'
|
||||
import Close from '@/shared/components/close'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEffect } from 'react'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
|
||||
export default function EditorTourTooltip({
|
||||
children,
|
||||
target,
|
||||
header,
|
||||
stage,
|
||||
placement,
|
||||
}: {
|
||||
children: React.ReactNode
|
||||
target: HTMLElement | null
|
||||
header: string
|
||||
stage: NewEditorTourStage
|
||||
placement?: OverlayProps['placement']
|
||||
}) {
|
||||
const { t } = useTranslation()
|
||||
const {
|
||||
shouldShowTourStage,
|
||||
dismissTour,
|
||||
goToNextStage,
|
||||
stageNumber,
|
||||
totalStages,
|
||||
finishTour,
|
||||
} = useNewEditorTourContext()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
const show = shouldShowTourStage(stage)
|
||||
|
||||
useEffect(() => {
|
||||
if (show) {
|
||||
sendEvent('new-editor-tour-shown', { stage: stageNumber })
|
||||
}
|
||||
}, [show, stageNumber, sendEvent])
|
||||
|
||||
const isFinalStage = stageNumber === totalStages
|
||||
|
||||
if (!show) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<Overlay show placement={placement} target={target} onHide={dismissTour}>
|
||||
<Popover className="editor-tour-tooltip">
|
||||
<Popover.Header>
|
||||
{header}
|
||||
<Close variant="dark" onDismiss={dismissTour} />
|
||||
</Popover.Header>
|
||||
<Popover.Body>
|
||||
{children}
|
||||
<div className="editor-tour-tooltip-footer">
|
||||
<div>
|
||||
{stageNumber}/{totalStages}
|
||||
</div>
|
||||
{isFinalStage ? (
|
||||
<OLButton onClick={finishTour} variant="link">
|
||||
{t('finish')}
|
||||
</OLButton>
|
||||
) : (
|
||||
<OLButton onClick={goToNextStage} variant="link">
|
||||
{t('next')}
|
||||
</OLButton>
|
||||
)}
|
||||
</div>
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
</Overlay>
|
||||
)
|
||||
}
|
||||
@@ -31,7 +31,6 @@ export default function MainLayout() {
|
||||
pdfIsOpen: isPdfOpen,
|
||||
pdfPanelRef,
|
||||
} = usePdfPane()
|
||||
|
||||
const { view, pdfLayout } = useLayoutContext()
|
||||
|
||||
const editorIsOpen =
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import {
|
||||
OLModal,
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/shared/components/ol/ol-modal'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import { useIsNewEditorEnabledAsExistingUser } from '../utils/new-editor-utils'
|
||||
import { useNewEditorTourContext } from '../contexts/new-editor-tour-context'
|
||||
import promoVideo from './new-editor-promo-video.mp4'
|
||||
|
||||
const TUTORIAL_KEY = 'new-editor-intro'
|
||||
|
||||
export default function NewEditorIntroModal() {
|
||||
const { inactiveTutorials } = useEditorContext()
|
||||
const {
|
||||
tryShowingPopup,
|
||||
showPopup: showModal,
|
||||
dismissTutorial,
|
||||
completeTutorial,
|
||||
clearPopup,
|
||||
} = useTutorial(TUTORIAL_KEY, {
|
||||
name: TUTORIAL_KEY,
|
||||
})
|
||||
const { startTour } = useNewEditorTourContext()
|
||||
|
||||
const { t } = useTranslation()
|
||||
|
||||
const canShow = useIsNewEditorEnabledAsExistingUser()
|
||||
const [hasShown, setHasShown] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (canShow && !hasShown && !inactiveTutorials.includes(TUTORIAL_KEY)) {
|
||||
tryShowingPopup('notification-prompt')
|
||||
setHasShown(true)
|
||||
}
|
||||
}, [tryShowingPopup, inactiveTutorials, canShow, hasShown])
|
||||
|
||||
const startProductTour = useCallback(() => {
|
||||
completeTutorial({ event: 'notification-click', action: 'complete' })
|
||||
startTour()
|
||||
clearPopup()
|
||||
}, [completeTutorial, startTour, clearPopup])
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
dismissTutorial('notification-dismiss')
|
||||
clearPopup()
|
||||
}, [dismissTutorial, clearPopup])
|
||||
|
||||
if (!canShow) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<OLModal show={showModal} onHide={closeModal}>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('introducing_overleafs_new_look')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody className="new-editor-intro-modal-body">
|
||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||
<video autoPlay loop muted>
|
||||
<source src={promoVideo} type="video/mp4" />
|
||||
</video>
|
||||
<div>
|
||||
{t('weve_made_it_easier_to_find_and_use_the_tools_you_need_today')}
|
||||
</div>
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton onClick={startProductTour} variant="primary">
|
||||
{t('explore_what_s_new')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import {
|
||||
OLModal,
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/shared/components/ol/ol-modal'
|
||||
import { useSwitchEnableNewEditorState } from '../hooks/use-switch-enable-new-editor-state'
|
||||
import { useCallback, useEffect, useState } from 'react'
|
||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import { Trans, useTranslation } from 'react-i18next'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
import {
|
||||
canUseNewEditorAsExistingUser,
|
||||
useIsNewEditorEnabled,
|
||||
} from '../utils/new-editor-utils'
|
||||
import promoVideo from './new-editor-promo-video.mp4'
|
||||
|
||||
const TUTORIAL_KEY = 'new-editor-opt-in'
|
||||
|
||||
export default function NewEditorPromoModal() {
|
||||
const { inactiveTutorials } = useEditorContext()
|
||||
const {
|
||||
tryShowingPopup,
|
||||
showPopup: showModal,
|
||||
dismissTutorial,
|
||||
completeTutorial,
|
||||
clearPopup,
|
||||
} = useTutorial(TUTORIAL_KEY, {
|
||||
name: TUTORIAL_KEY,
|
||||
})
|
||||
const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
|
||||
const { t } = useTranslation()
|
||||
|
||||
const newEditor = useIsNewEditorEnabled()
|
||||
const canShow = canUseNewEditorAsExistingUser() && !newEditor
|
||||
const [hasShown, setHasShown] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (canShow && !hasShown && !inactiveTutorials.includes(TUTORIAL_KEY)) {
|
||||
tryShowingPopup('notification-prompt')
|
||||
setHasShown(true)
|
||||
}
|
||||
}, [tryShowingPopup, inactiveTutorials, canShow, hasShown])
|
||||
|
||||
const switchToNewEditor = useCallback(() => {
|
||||
setEditorRedesignStatus(true)
|
||||
completeTutorial({ event: 'notification-click', action: 'complete' })
|
||||
clearPopup()
|
||||
}, [setEditorRedesignStatus, completeTutorial, clearPopup])
|
||||
|
||||
const closeModal = useCallback(() => {
|
||||
dismissTutorial('notification-dismiss')
|
||||
clearPopup()
|
||||
}, [dismissTutorial, clearPopup])
|
||||
|
||||
if (!canShow) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<OLModal show={showModal} onHide={closeModal}>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>{t('overleafs_new_look_is_here')}</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody className="new-editor-promo-modal-body">
|
||||
<div>
|
||||
{t(
|
||||
'be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor'
|
||||
)}
|
||||
</div>
|
||||
{/* eslint-disable-next-line jsx-a11y/media-has-caption */}
|
||||
<video autoPlay loop muted>
|
||||
<source src={promoVideo} type="video/mp4" />
|
||||
</video>
|
||||
<div>
|
||||
<Trans
|
||||
i18nKey="try_out_the_new_editor_now"
|
||||
components={[
|
||||
/* eslint-disable-next-line jsx-a11y/anchor-has-content, react/jsx-key */
|
||||
<a
|
||||
href="https://www.overleaf.com/blog/introducing-overleafs-new-look"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
key="link"
|
||||
/>,
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton onClick={closeModal} variant="secondary">
|
||||
{t('not_now')}
|
||||
</OLButton>
|
||||
<OLButton onClick={switchToNewEditor} variant="primary">
|
||||
{t('try_the_new_look')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
Binary file not shown.
+11
-2
@@ -1,4 +1,4 @@
|
||||
import { memo } from 'react'
|
||||
import { memo, useCallback, useState } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import OLButtonToolbar from '@/shared/components/ol/ol-button-toolbar'
|
||||
import PdfCompileButton from '@/features/pdf-preview/components/pdf-compile-button'
|
||||
@@ -6,10 +6,18 @@ import PdfHybridDownloadButton from '@/features/pdf-preview/components/pdf-hybri
|
||||
import { DetachedSynctexControl } from '@/features/pdf-preview/components/detach-synctex-control'
|
||||
import SwitchToEditorButton from '@/features/pdf-preview/components/switch-to-editor-button'
|
||||
import PdfHybridLogsButton from '@/features/pdf-preview/components/pdf-hybrid-logs-button'
|
||||
import EditorTourLogsTooltip from '../editor-tour/editor-tour-logs-tooltip'
|
||||
|
||||
function PdfPreviewHybridToolbar() {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const [logsButtonElt, setLogsButtonElt] = useState<HTMLElement | null>(null)
|
||||
const logsButtonRef = useCallback((node: HTMLButtonElement) => {
|
||||
if (node !== null) {
|
||||
setLogsButtonElt(node)
|
||||
}
|
||||
}, [])
|
||||
|
||||
// TODO: add detached pdf logic
|
||||
return (
|
||||
<OLButtonToolbar
|
||||
@@ -18,8 +26,9 @@ function PdfPreviewHybridToolbar() {
|
||||
>
|
||||
<div className="toolbar-pdf-left">
|
||||
<PdfCompileButton />
|
||||
<PdfHybridLogsButton />
|
||||
<PdfHybridLogsButton ref={logsButtonRef} />
|
||||
<PdfHybridDownloadButton />
|
||||
<EditorTourLogsTooltip target={logsButtonElt} />
|
||||
</div>
|
||||
<div className="toolbar-pdf-right">
|
||||
<div className="toolbar-pdf-controls" id="toolbar-pdf-controls" />
|
||||
|
||||
+63
-52
@@ -1,12 +1,13 @@
|
||||
import MaterialIcon, {
|
||||
AvailableUnfilledIcon,
|
||||
} from '@/shared/components/material-icon'
|
||||
import { ReactElement, useCallback } from 'react'
|
||||
import { forwardRef, ReactElement, useCallback } from 'react'
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownToggle,
|
||||
} from '@/shared/components/dropdown/dropdown-menu'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import { DropdownToggle as BS5DropdownToggle } from 'react-bootstrap'
|
||||
|
||||
type RailActionButton = {
|
||||
key: string
|
||||
@@ -14,6 +15,7 @@ type RailActionButton = {
|
||||
title: string
|
||||
action: () => void
|
||||
hide?: boolean
|
||||
ref?: React.Ref<HTMLButtonElement>
|
||||
}
|
||||
|
||||
type RailDropdown = {
|
||||
@@ -22,66 +24,75 @@ type RailDropdown = {
|
||||
title: string
|
||||
dropdown: ReactElement
|
||||
hide?: boolean
|
||||
ref?: React.Ref<HTMLButtonElement>
|
||||
}
|
||||
|
||||
export type RailAction = RailDropdown | RailActionButton
|
||||
|
||||
export default function RailActionElement({ action }: { action: RailAction }) {
|
||||
const onActionClick = useCallback(() => {
|
||||
if ('action' in action) {
|
||||
action.action()
|
||||
const RailActionElement = forwardRef<HTMLButtonElement, { action: RailAction }>(
|
||||
({ action }, ref) => {
|
||||
const onActionClick = useCallback(() => {
|
||||
if ('action' in action) {
|
||||
action.action()
|
||||
}
|
||||
}, [action])
|
||||
|
||||
if (action.hide) {
|
||||
return null
|
||||
}
|
||||
}, [action])
|
||||
|
||||
if (action.hide) {
|
||||
return null
|
||||
}
|
||||
|
||||
if ('dropdown' in action) {
|
||||
return (
|
||||
<Dropdown align="end" drop="end">
|
||||
if ('dropdown' in action) {
|
||||
return (
|
||||
<Dropdown align="end" drop="end">
|
||||
<OLTooltip
|
||||
id={`rail-dropdown-tooltip-${action.key}`}
|
||||
description={action.title}
|
||||
overlayProps={{ delay: 0, placement: 'right' }}
|
||||
>
|
||||
<span>
|
||||
<DropdownToggle
|
||||
ref={ref as React.ForwardedRef<typeof BS5DropdownToggle>}
|
||||
id={`rail-dropdown-btn-${action.key}`}
|
||||
className="ide-rail-tab-link ide-rail-tab-button ide-rail-tab-dropdown"
|
||||
as="button"
|
||||
aria-label={action.title}
|
||||
>
|
||||
<MaterialIcon
|
||||
className="ide-rail-tab-link-icon"
|
||||
type={action.icon}
|
||||
unfilled
|
||||
/>
|
||||
</DropdownToggle>
|
||||
</span>
|
||||
</OLTooltip>
|
||||
{action.dropdown}
|
||||
</Dropdown>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<OLTooltip
|
||||
id={`rail-dropdown-tooltip-${action.key}`}
|
||||
id={`rail-tab-tooltip-${action.key}`}
|
||||
description={action.title}
|
||||
overlayProps={{ delay: 0, placement: 'right' }}
|
||||
>
|
||||
<span>
|
||||
<DropdownToggle
|
||||
id={`rail-dropdown-btn-${action.key}`}
|
||||
className="ide-rail-tab-link ide-rail-tab-button ide-rail-tab-dropdown"
|
||||
as="button"
|
||||
aria-label={action.title}
|
||||
>
|
||||
<MaterialIcon
|
||||
className="ide-rail-tab-link-icon"
|
||||
type={action.icon}
|
||||
unfilled
|
||||
/>
|
||||
</DropdownToggle>
|
||||
</span>
|
||||
<button
|
||||
ref={ref}
|
||||
onClick={onActionClick}
|
||||
className="ide-rail-tab-link ide-rail-tab-button"
|
||||
aria-label={action.title}
|
||||
>
|
||||
<MaterialIcon
|
||||
className="ide-rail-tab-link-icon"
|
||||
type={action.icon}
|
||||
unfilled
|
||||
/>
|
||||
</button>
|
||||
</OLTooltip>
|
||||
{action.dropdown}
|
||||
</Dropdown>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<OLTooltip
|
||||
id={`rail-tab-tooltip-${action.key}`}
|
||||
description={action.title}
|
||||
overlayProps={{ delay: 0, placement: 'right' }}
|
||||
>
|
||||
<button
|
||||
onClick={onActionClick}
|
||||
className="ide-rail-tab-link ide-rail-tab-button"
|
||||
aria-label={action.title}
|
||||
>
|
||||
<MaterialIcon
|
||||
className="ide-rail-tab-link-icon"
|
||||
type={action.icon}
|
||||
unfilled
|
||||
/>
|
||||
</button>
|
||||
</OLTooltip>
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
RailActionElement.displayName = 'RailActionElement'
|
||||
|
||||
export default RailActionElement
|
||||
|
||||
@@ -2,7 +2,6 @@ import getMeta from '@/utils/meta'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useRailContext } from '../../contexts/rail-context'
|
||||
import { useCallback } from 'react'
|
||||
import { useSurveyUrl } from '../../hooks/use-survey-url'
|
||||
import {
|
||||
DropdownDivider,
|
||||
DropdownItem,
|
||||
@@ -19,7 +18,6 @@ export default function RailHelpDropdown() {
|
||||
const openContactUsModal = useCallback(() => {
|
||||
setActiveModal('contact-us')
|
||||
}, [setActiveModal])
|
||||
const surveyURL = useSurveyUrl()
|
||||
|
||||
return (
|
||||
<DropdownMenu>
|
||||
@@ -40,14 +38,6 @@ export default function RailHelpDropdown() {
|
||||
{t('contact_us')}
|
||||
</DropdownItem>
|
||||
)}
|
||||
<DropdownItem
|
||||
href={surveyURL}
|
||||
role="menuitem"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('give_feedback')}
|
||||
</DropdownItem>
|
||||
</DropdownMenu>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { useCallback, useEffect, useMemo } from 'react'
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Nav, TabContainer } from 'react-bootstrap'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
@@ -26,7 +26,10 @@ import RailResizeHandle from './rail-resize-handle'
|
||||
import RailModals from './rail-modals'
|
||||
import RailOverflowDropdown from './rail-overflow-dropdown'
|
||||
import useRailOverflow from '../../hooks/use-rail-overflow'
|
||||
import EditorTourRailTooltip from '../editor-tour/editor-tour-rail-tooltip'
|
||||
import importOverleafModules from '../../../../../macros/import-overleaf-module.macro'
|
||||
import EditorTourThemeTooltip from '../editor-tour/editor-tour-theme-tooltip'
|
||||
import EditorTourSwitchBackTooltip from '../editor-tour/editor-tour-switch-back-tooltip'
|
||||
|
||||
const moduleRailEntries = (
|
||||
importOverleafModules('railEntries') as {
|
||||
@@ -47,6 +50,9 @@ export const RailLayout = () => {
|
||||
|
||||
const isHistoryView = view === 'history'
|
||||
|
||||
const fileTreeRef = useRef<HTMLAnchorElement>(null)
|
||||
const settingsRef = useRef<HTMLButtonElement>(null)
|
||||
|
||||
const railTabs: RailElement[] = useMemo(
|
||||
() => [
|
||||
{
|
||||
@@ -57,6 +63,7 @@ export const RailLayout = () => {
|
||||
// NOTE: We always need to mount the file tree on first load
|
||||
// since it is responsible for opening the initial document.
|
||||
mountOnFirstLoad: true,
|
||||
ref: fileTreeRef,
|
||||
},
|
||||
{
|
||||
key: 'full-project-search',
|
||||
@@ -108,6 +115,7 @@ export const RailLayout = () => {
|
||||
sendEvent('rail-click', { tab: 'settings' })
|
||||
setLeftMenuShown(true)
|
||||
},
|
||||
ref: settingsRef,
|
||||
},
|
||||
],
|
||||
[setLeftMenuShown, t, sendEvent]
|
||||
@@ -219,7 +227,7 @@ export const RailLayout = () => {
|
||||
.filter(({ hide }) =>
|
||||
typeof hide === 'function' ? !hide() : !hide
|
||||
)
|
||||
.map(({ icon, key, indicator, title, disabled }) => (
|
||||
.map(({ icon, key, indicator, title, disabled, ref }) => (
|
||||
<RailTab
|
||||
open={isOpen && selectedTab === key}
|
||||
key={key}
|
||||
@@ -228,17 +236,25 @@ export const RailLayout = () => {
|
||||
indicator={indicator}
|
||||
title={title}
|
||||
disabled={disabled}
|
||||
ref={ref}
|
||||
/>
|
||||
))}
|
||||
<RailActionElement key="more-options" action={moreOptionsAction} />
|
||||
</div>
|
||||
<nav aria-label={t('help_editor_settings')}>
|
||||
{railActions.map(action => (
|
||||
<RailActionElement key={action.key} action={action} />
|
||||
<RailActionElement
|
||||
key={action.key}
|
||||
action={action}
|
||||
ref={action.ref}
|
||||
/>
|
||||
))}
|
||||
</nav>
|
||||
</Nav>
|
||||
</nav>
|
||||
<EditorTourRailTooltip target={fileTreeRef.current} />
|
||||
<EditorTourThemeTooltip target={settingsRef.current} />
|
||||
<EditorTourSwitchBackTooltip target={settingsRef.current} />
|
||||
<RailPanel
|
||||
isReviewPanelOpen={isReviewPanelOpen}
|
||||
isHistoryView={isHistoryView}
|
||||
|
||||
+17
-7
@@ -4,26 +4,36 @@ import { useSwitchEnableNewEditorState } from '@/features/ide-redesign/hooks/use
|
||||
import { useIsNewEditorEnabled } from '@/features/ide-redesign/utils/new-editor-utils'
|
||||
import { useCallback } from 'react'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
|
||||
export default function NewEditorSetting() {
|
||||
const { t } = useTranslation()
|
||||
const { setEditorRedesignStatus } = useSwitchEnableNewEditorState()
|
||||
const { setLeftMenuShown } = useLayoutContext()
|
||||
const enabled = useIsNewEditorEnabled()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
const handleToggle = useCallback(() => {
|
||||
sendEvent('switch-to-old-editor', { location: 'settings-modal' })
|
||||
setEditorRedesignStatus(!enabled).then(() => setLeftMenuShown(false))
|
||||
}, [enabled, setEditorRedesignStatus, setLeftMenuShown])
|
||||
}, [enabled, setEditorRedesignStatus, setLeftMenuShown, sendEvent])
|
||||
|
||||
return (
|
||||
<ToggleSetting
|
||||
id="new-editor-setting"
|
||||
label={
|
||||
<div className="ide-setting-new-editor">
|
||||
{t('new_editor_experience')}
|
||||
<div className="ide-setting-beta-tag">{t('beta')}</div>
|
||||
</div>
|
||||
label={t('new_editor_look')}
|
||||
description={
|
||||
<>
|
||||
<div>{t('the_new_overleaf_editor_info')}</div>
|
||||
<a
|
||||
href="https://forms.gle/3tPYhXcBVGmUB2HXA"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{t('share_feedback_on_the_new_editor')}
|
||||
</a>
|
||||
</>
|
||||
}
|
||||
description={t('new_editor_info')}
|
||||
checked={enabled}
|
||||
onChange={handleToggle}
|
||||
/>
|
||||
|
||||
@@ -7,7 +7,7 @@ export default function Setting({
|
||||
description = undefined,
|
||||
}: {
|
||||
label: React.ReactNode
|
||||
description: string | undefined
|
||||
description: React.ReactNode | undefined
|
||||
controlId: string
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ export default function ToggleSetting({
|
||||
}: {
|
||||
id: string
|
||||
label: React.ReactNode
|
||||
description: string
|
||||
description: React.ReactNode
|
||||
checked: boolean | undefined
|
||||
onChange: (newValue: boolean) => void
|
||||
disabled?: boolean
|
||||
|
||||
-246
@@ -1,246 +0,0 @@
|
||||
import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import {
|
||||
OLModal,
|
||||
OLModalBody,
|
||||
OLModalFooter,
|
||||
OLModalHeader,
|
||||
OLModalTitle,
|
||||
} from '@/shared/components/ol/ol-modal'
|
||||
import { FC, useCallback, useEffect } from 'react'
|
||||
import {
|
||||
canUseNewEditor,
|
||||
useIsNewEditorEnabled,
|
||||
useIsNewEditorEnabledAsExistingUser,
|
||||
} from '../../utils/new-editor-utils'
|
||||
import Notification from '@/shared/components/notification'
|
||||
import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
import { useSurveyUrl } from '../../hooks/use-survey-url'
|
||||
import useTutorial from '@/shared/hooks/promotions/use-tutorial'
|
||||
import { useEditorContext } from '@/shared/context/editor-context'
|
||||
|
||||
const TUTORIAL_KEY = 'ide-redesign-beta-intro'
|
||||
|
||||
export const IdeRedesignIntroModal: FC = () => {
|
||||
const { t } = useTranslation()
|
||||
const { inactiveTutorials } = useEditorContext()
|
||||
const { showPopup, tryShowingPopup, dismissTutorial } = useTutorial(
|
||||
TUTORIAL_KEY,
|
||||
{
|
||||
name: TUTORIAL_KEY,
|
||||
}
|
||||
)
|
||||
const hasAccess = useIsNewEditorEnabledAsExistingUser()
|
||||
|
||||
useEffect(() => {
|
||||
if (!hasAccess) return
|
||||
if (!inactiveTutorials.includes(TUTORIAL_KEY)) {
|
||||
tryShowingPopup()
|
||||
}
|
||||
}, [tryShowingPopup, inactiveTutorials, hasAccess])
|
||||
|
||||
if (!hasAccess) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<OLModal
|
||||
show={showPopup}
|
||||
onHide={dismissTutorial}
|
||||
className="ide-redesign-switcher-modal"
|
||||
>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>
|
||||
{t('the_new_overleaf_editor_try_now_in_beta')}
|
||||
</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
{t(
|
||||
'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready'
|
||||
)}{' '}
|
||||
{t('you_can_switch_back_to_the_old_editor_at_any_time')}
|
||||
</p>
|
||||
<SwitcherWhatsDifferent />
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton onClick={dismissTutorial} variant="secondary">
|
||||
{t('close')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
export const IdeRedesignSwitcherModal = () => {
|
||||
const { t } = useTranslation()
|
||||
const { showSwitcherModal, setShowSwitcherModal } =
|
||||
useIdeRedesignSwitcherContext()
|
||||
const onHide = useCallback(
|
||||
() => setShowSwitcherModal(false),
|
||||
[setShowSwitcherModal]
|
||||
)
|
||||
const { loading, error, setEditorRedesignStatus } =
|
||||
useSwitchEnableNewEditorState()
|
||||
const enabled = useIsNewEditorEnabled()
|
||||
const hasAccess = canUseNewEditor()
|
||||
if (!hasAccess) {
|
||||
return null
|
||||
}
|
||||
|
||||
const Content = enabled
|
||||
? SwitcherModalContentEnabled
|
||||
: SwitcherModalContentDisabled
|
||||
|
||||
return (
|
||||
<OLModal
|
||||
show={showSwitcherModal}
|
||||
onHide={onHide}
|
||||
className="ide-redesign-switcher-modal"
|
||||
>
|
||||
<OLModalHeader>
|
||||
<OLModalTitle>
|
||||
{enabled
|
||||
? t('beta_program_the_new_overleaf_editor')
|
||||
: t('the_new_overleaf_editor_try_now_in_beta')}
|
||||
</OLModalTitle>
|
||||
</OLModalHeader>
|
||||
{error && <Notification type="error" content={error} isDismissible />}
|
||||
<Content
|
||||
setEditorRedesignStatus={setEditorRedesignStatus}
|
||||
hide={onHide}
|
||||
loading={loading}
|
||||
/>
|
||||
</OLModal>
|
||||
)
|
||||
}
|
||||
|
||||
type ModalContentProps = {
|
||||
setEditorRedesignStatus: (enabled: boolean) => Promise<void>
|
||||
hide: () => void
|
||||
loading: boolean
|
||||
}
|
||||
|
||||
const SwitcherModalContentEnabled: FC<ModalContentProps> = ({
|
||||
setEditorRedesignStatus,
|
||||
hide,
|
||||
loading,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
const disable = useCallback(() => {
|
||||
sendEvent('editor-redesign-toggle', {
|
||||
action: 'disable',
|
||||
location: 'modal',
|
||||
})
|
||||
setEditorRedesignStatus(false)
|
||||
.then(hide)
|
||||
.catch(() => {
|
||||
// do nothing, we're already showing the error
|
||||
})
|
||||
}, [setEditorRedesignStatus, hide, sendEvent])
|
||||
|
||||
const surveyURL = useSurveyUrl()
|
||||
|
||||
return (
|
||||
<>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
{t(
|
||||
'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready'
|
||||
)}
|
||||
</p>
|
||||
<SwitcherWhatsDifferent />
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton
|
||||
onClick={disable}
|
||||
variant="secondary"
|
||||
className="me-auto"
|
||||
disabled={loading}
|
||||
>
|
||||
{t('switch_to_old_editor')}
|
||||
</OLButton>
|
||||
<OLButton onClick={hide} variant="secondary">
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
<OLButton
|
||||
href={surveyURL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
variant="primary"
|
||||
>
|
||||
{t('give_feedback')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const SwitcherModalContentDisabled: FC<ModalContentProps> = ({
|
||||
setEditorRedesignStatus,
|
||||
hide,
|
||||
loading,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
const enable = useCallback(() => {
|
||||
sendEvent('editor-redesign-toggle', {
|
||||
action: 'enable',
|
||||
location: 'modal',
|
||||
})
|
||||
setEditorRedesignStatus(true)
|
||||
.then(hide)
|
||||
.catch(() => {
|
||||
// do nothing, we're already showing the error
|
||||
})
|
||||
}, [setEditorRedesignStatus, hide, sendEvent])
|
||||
return (
|
||||
<>
|
||||
<OLModalBody>
|
||||
<p>
|
||||
{t(
|
||||
'weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready'
|
||||
)}
|
||||
</p>
|
||||
<SwitcherWhatsDifferent />
|
||||
<LeavingNote />
|
||||
</OLModalBody>
|
||||
<OLModalFooter>
|
||||
<OLButton onClick={hide} variant="secondary">
|
||||
{t('cancel')}
|
||||
</OLButton>
|
||||
<OLButton onClick={enable} variant="primary" disabled={loading}>
|
||||
{t('switch_to_new_editor')}
|
||||
</OLButton>
|
||||
</OLModalFooter>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const SwitcherWhatsDifferent = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<div className="ide-redesign-switcher-modal-whats-new">
|
||||
<h4>{t('whats_different')}</h4>
|
||||
<ul>
|
||||
<li>{t('new_look_and_feel')}</li>
|
||||
<li>
|
||||
{t('new_navigation_introducing_left_hand_side_rail_and_top_menus')}
|
||||
</li>
|
||||
<li>{t('new_look_and_placement_of_the_settings')}</li>
|
||||
<li>{t('improved_dark_mode')}</li>
|
||||
<li>{t('review_panel_and_error_logs_moved_to_the_left')}</li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const LeavingNote = () => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
return <p>{t('you_can_switch_back_to_the_old_editor_at_any_time')}</p>
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import { useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { GiveFeedbackLink } from './give-feedback-link'
|
||||
import { useIsNewEditorEnabledAsExistingUser } from '../../utils/new-editor-utils'
|
||||
|
||||
export const BetaActions = () => {
|
||||
const { t } = useTranslation()
|
||||
const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
|
||||
const openEditorRedesignSwitcherModal = useCallback(() => {
|
||||
setShowSwitcherModal(true)
|
||||
}, [setShowSwitcherModal])
|
||||
const showBetaActions = useIsNewEditorEnabledAsExistingUser()
|
||||
|
||||
if (!showBetaActions) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="ide-redesign-toolbar-button-container">
|
||||
<OLTooltip
|
||||
id="tooltip-beta-button"
|
||||
description={t('this_is_a_beta_release_for_the_new_overleaf_editor')}
|
||||
overlayProps={{ delay: 0, placement: 'bottom' }}
|
||||
>
|
||||
<OLButton
|
||||
size="sm"
|
||||
variant="secondary"
|
||||
className="ide-redesign-beta-button"
|
||||
onClick={openEditorRedesignSwitcherModal}
|
||||
>
|
||||
{t('beta')}
|
||||
</OLButton>
|
||||
</OLTooltip>
|
||||
</div>
|
||||
<GiveFeedbackLink />
|
||||
</>
|
||||
)
|
||||
}
|
||||
-20
@@ -1,20 +0,0 @@
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { useSurveyUrl } from '../../hooks/use-survey-url'
|
||||
|
||||
export const GiveFeedbackLink = () => {
|
||||
const { t } = useTranslation()
|
||||
const surveyURL = useSurveyUrl()
|
||||
|
||||
return (
|
||||
<div className="ide-redesign-toolbar-button-container">
|
||||
<a
|
||||
href={surveyURL}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
className="ide-redesign-toolbar-labs-feedback-link"
|
||||
>
|
||||
{t('give_feedback')}
|
||||
</a>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -8,11 +8,7 @@ import { MenuBarDropdown } from '@/shared/components/menu-bar/menu-bar-dropdown'
|
||||
import { MenuBarOption } from '@/shared/components/menu-bar/menu-bar-option'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import ChangeLayoutOptions from './change-layout-options'
|
||||
import { MouseEventHandler, useCallback, useMemo, useState } from 'react'
|
||||
import { useIdeRedesignSwitcherContext } from '@/features/ide-react/context/ide-redesign-switcher-context'
|
||||
import { useSwitchEnableNewEditorState } from '../../hooks/use-switch-enable-new-editor-state'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import OLSpinner from '@/shared/components/ol/ol-spinner'
|
||||
import { useCallback, useMemo, useState } from 'react'
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
import { useCommandProvider } from '@/features/ide-react/hooks/use-command-provider'
|
||||
import CommandDropdown, {
|
||||
@@ -24,27 +20,20 @@ import { useRailContext } from '../../contexts/rail-context'
|
||||
import WordCountModal from '@/features/word-count-modal/components/word-count-modal'
|
||||
import { isSplitTestEnabled } from '@/utils/splitTestUtils'
|
||||
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
|
||||
import { useEditorAnalytics } from '@/shared/hooks/use-editor-analytics'
|
||||
import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import { useSurveyUrl } from '../../hooks/use-survey-url'
|
||||
import getMeta from '@/utils/meta'
|
||||
import EditorCloneProjectModalWrapper from '@/features/clone-project-modal/components/editor-clone-project-modal-wrapper'
|
||||
import useOpenProject from '@/shared/hooks/use-open-project'
|
||||
import { canUseNewEditorAsExistingUser } from '../../utils/new-editor-utils'
|
||||
|
||||
export const ToolbarMenuBar = () => {
|
||||
const { t } = useTranslation()
|
||||
const { setShowSwitcherModal } = useIdeRedesignSwitcherContext()
|
||||
const openEditorRedesignSwitcherModal = useCallback(() => {
|
||||
setShowSwitcherModal(true)
|
||||
}, [setShowSwitcherModal])
|
||||
|
||||
const { setView, view } = useLayoutContext()
|
||||
const { pdfUrl } = useCompileContext()
|
||||
const wordCountEnabled = pdfUrl || isSplitTestEnabled('word-count-client')
|
||||
const [showWordCountModal, setShowWordCountModal] = useState(false)
|
||||
const [showCloneProjectModal, setShowCloneProjectModal] = useState(false)
|
||||
const openProject = useOpenProject()
|
||||
const showEditorSwitchMenuOption = canUseNewEditorAsExistingUser()
|
||||
|
||||
const anonymous = getMeta('ol-anonymous')
|
||||
|
||||
@@ -213,8 +202,6 @@ export const ToolbarMenuBar = () => {
|
||||
setActiveModal('contact-us')
|
||||
}, [setActiveModal])
|
||||
|
||||
const surveyURL = useSurveyUrl()
|
||||
|
||||
return (
|
||||
<>
|
||||
<MenuBar
|
||||
@@ -285,24 +272,6 @@ export const ToolbarMenuBar = () => {
|
||||
title={t('contact_us')}
|
||||
onClick={openContactUsModal}
|
||||
/>
|
||||
{showEditorSwitchMenuOption && (
|
||||
<>
|
||||
<MenuBarOption
|
||||
eventKey="give_feedback"
|
||||
title={t('give_feedback')}
|
||||
href={surveyURL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
/>
|
||||
<DropdownDivider />
|
||||
<SwitchToOldEditorMenuBarOption />
|
||||
<MenuBarOption
|
||||
eventKey="whats_new"
|
||||
title="What's new?"
|
||||
onClick={openEditorRedesignSwitcherModal}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</MenuBarDropdown>
|
||||
</MenuBar>
|
||||
<WordCountModal
|
||||
@@ -317,37 +286,3 @@ export const ToolbarMenuBar = () => {
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const SwitchToOldEditorMenuBarOption = () => {
|
||||
const { loading, error, setEditorRedesignStatus } =
|
||||
useSwitchEnableNewEditorState()
|
||||
const { sendEvent } = useEditorAnalytics()
|
||||
|
||||
const disable: MouseEventHandler = useCallback(
|
||||
event => {
|
||||
// Don't close the dropdown
|
||||
event.stopPropagation()
|
||||
sendEvent('editor-redesign-toggle', {
|
||||
action: 'disable',
|
||||
location: 'menu-bar',
|
||||
})
|
||||
setEditorRedesignStatus(false)
|
||||
},
|
||||
[setEditorRedesignStatus, sendEvent]
|
||||
)
|
||||
let icon = null
|
||||
if (loading) {
|
||||
icon = <OLSpinner size="sm" />
|
||||
} else if (error) {
|
||||
icon = <MaterialIcon type="error" title={error} className="text-danger" />
|
||||
}
|
||||
return (
|
||||
<MenuBarOption
|
||||
eventKey="switch_to_old_editor"
|
||||
title="Switch to old editor"
|
||||
onClick={disable}
|
||||
disabled={loading}
|
||||
trailingIcon={icon}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import importOverleafModules from '../../../../../macros/import-overleaf-module.
|
||||
import UpgradeButton from './upgrade-button'
|
||||
import getMeta from '@/utils/meta'
|
||||
import { useIdeReactContext } from '@/features/ide-react/context/ide-react-context'
|
||||
import { BetaActions } from './beta-actions'
|
||||
|
||||
const [publishModalModules] = importOverleafModules('publishModal')
|
||||
const SubmitProjectButton = publishModalModules?.import.NewPublishToolbarButton
|
||||
@@ -54,7 +53,6 @@ export const Toolbar = () => {
|
||||
</div>
|
||||
<ToolbarProjectTitle />
|
||||
<div className="ide-redesign-toolbar-actions">
|
||||
<BetaActions />
|
||||
<OnlineUsers />
|
||||
{!isRestrictedTokenMember && <ShowHistoryButton />}
|
||||
<ChangeLayoutButton />
|
||||
|
||||
@@ -44,6 +44,10 @@ export default function TooltipPromotion({
|
||||
hideUntilReload()
|
||||
}, [hideUntilReload])
|
||||
|
||||
const onDismiss = useCallback(() => {
|
||||
dismissTutorial()
|
||||
}, [dismissTutorial])
|
||||
|
||||
if (!target || !isInSplitTestIfNeeded) {
|
||||
return null
|
||||
}
|
||||
@@ -60,13 +64,13 @@ export default function TooltipPromotion({
|
||||
{header && (
|
||||
<Popover.Header>
|
||||
{header}
|
||||
<Close variant="dark" onDismiss={dismissTutorial} />
|
||||
<Close variant="dark" onDismiss={onDismiss} />
|
||||
</Popover.Header>
|
||||
)}
|
||||
|
||||
<Popover.Body className={classNames(className)}>
|
||||
{content}
|
||||
{!header && <Close variant="dark" onDismiss={dismissTutorial} />}
|
||||
{!header && <Close variant="dark" onDismiss={onDismiss} />}
|
||||
</Popover.Body>
|
||||
</Popover>
|
||||
</Overlay>
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
import { useLayoutContext } from '@/shared/context/layout-context'
|
||||
import {
|
||||
createContext,
|
||||
FC,
|
||||
useCallback,
|
||||
useContext,
|
||||
useMemo,
|
||||
useState,
|
||||
} from 'react'
|
||||
|
||||
export type NewEditorTourStage = 'rail' | 'logs' | 'theme' | 'switch-back'
|
||||
|
||||
const NewEditorTourContext = createContext<
|
||||
| {
|
||||
stage: NewEditorTourStage
|
||||
stageNumber: number
|
||||
totalStages: number
|
||||
shouldShowTourStage: (tourStage: NewEditorTourStage) => boolean
|
||||
startTour: () => void
|
||||
goToNextStage: () => void
|
||||
finishTour: () => void
|
||||
dismissTour: () => void
|
||||
}
|
||||
| undefined
|
||||
>(undefined)
|
||||
|
||||
const STAGES: NewEditorTourStage[] = ['rail', 'logs', 'theme', 'switch-back']
|
||||
const EDITOR_ONLY_STAGES: NewEditorTourStage[] = [
|
||||
'rail',
|
||||
'theme',
|
||||
'switch-back',
|
||||
]
|
||||
|
||||
export const NewEditorTourProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const [stage, setStage] = useState<NewEditorTourStage>('rail')
|
||||
const [showTour, setShowTour] = useState(false)
|
||||
const { view, pdfLayout } = useLayoutContext()
|
||||
const pdfIsOpen = pdfLayout === 'sideBySide' || view === 'pdf'
|
||||
|
||||
const stagesToShow = useMemo(
|
||||
() => (pdfIsOpen ? STAGES : EDITOR_ONLY_STAGES),
|
||||
[pdfIsOpen]
|
||||
)
|
||||
|
||||
const startTour = useCallback(() => {
|
||||
setShowTour(true)
|
||||
}, [])
|
||||
|
||||
const stageNumber = useMemo(
|
||||
() => stagesToShow.indexOf(stage) + 1,
|
||||
[stage, stagesToShow]
|
||||
)
|
||||
const totalStages = stagesToShow.length
|
||||
|
||||
const goToNextStage = useCallback(() => {
|
||||
setStage(stagesToShow[stageNumber])
|
||||
}, [stageNumber, stagesToShow])
|
||||
|
||||
const dismissTour = useCallback(() => {
|
||||
setShowTour(false)
|
||||
}, [])
|
||||
|
||||
const finishTour = useCallback(() => {
|
||||
setShowTour(false)
|
||||
}, [])
|
||||
|
||||
const shouldShowTourStage = useCallback(
|
||||
(tourStage: NewEditorTourStage) => {
|
||||
return showTour && stage === tourStage
|
||||
},
|
||||
[showTour, stage]
|
||||
)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
stage,
|
||||
stageNumber,
|
||||
totalStages,
|
||||
shouldShowTourStage,
|
||||
startTour,
|
||||
goToNextStage,
|
||||
finishTour,
|
||||
dismissTour,
|
||||
}),
|
||||
[
|
||||
stage,
|
||||
stageNumber,
|
||||
totalStages,
|
||||
shouldShowTourStage,
|
||||
startTour,
|
||||
goToNextStage,
|
||||
finishTour,
|
||||
dismissTour,
|
||||
]
|
||||
)
|
||||
|
||||
return (
|
||||
<NewEditorTourContext.Provider value={value}>
|
||||
{children}
|
||||
</NewEditorTourContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useNewEditorTourContext = () => {
|
||||
const context = useContext(NewEditorTourContext)
|
||||
if (!context) {
|
||||
throw new Error(
|
||||
'useNewEditorTourContext is only available inside RailProvider'
|
||||
)
|
||||
}
|
||||
return context
|
||||
}
|
||||
@@ -25,7 +25,6 @@ import FontFamilySetting from '../components/settings/appearance-settings/font-f
|
||||
import { AvailableUnfilledIcon } from '@/shared/components/material-icon'
|
||||
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
|
||||
import NewEditorSetting from '../components/settings/editor-settings/new-editor-setting'
|
||||
import { canUseNewEditorAsNewUser } from '../utils/new-editor-utils'
|
||||
|
||||
const [referenceSearchSettingModule] = importOverleafModules(
|
||||
'referenceSearchSetting'
|
||||
@@ -77,7 +76,6 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
children,
|
||||
}) => {
|
||||
const { t } = useTranslation()
|
||||
const showEditorSwitch = canUseNewEditorAsNewUser()
|
||||
|
||||
// TODO ide-redesign-cleanup: Rename this field and move it directly into this context
|
||||
const { leftMenuShown, setLeftMenuShown } = useLayoutContext()
|
||||
@@ -215,7 +213,6 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
{
|
||||
key: 'newEditor',
|
||||
component: <NewEditorSetting />,
|
||||
hidden: !showEditorSwitch,
|
||||
},
|
||||
],
|
||||
},
|
||||
@@ -234,7 +231,7 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
href: '/user/subscription',
|
||||
},
|
||||
],
|
||||
[t, showEditorSwitch]
|
||||
[t]
|
||||
)
|
||||
|
||||
const settingToTabMap = useMemo(() => {
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
import { useSplitTest } from '@/shared/context/split-test-context'
|
||||
|
||||
export const useSurveyUrl = () => {
|
||||
const splitTestConfig = useSplitTest('editor-redesign')
|
||||
|
||||
return (
|
||||
splitTestConfig.info?.badgeInfo?.url ||
|
||||
'https://forms.gle/NGkALNUiMbanjp3Q7'
|
||||
)
|
||||
}
|
||||
@@ -11,4 +11,5 @@ export type RailElement = {
|
||||
hide?: boolean | (() => boolean)
|
||||
disabled?: boolean
|
||||
mountOnFirstLoad?: boolean
|
||||
ref?: React.RefObject<HTMLAnchorElement>
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { memo, useCallback } from 'react'
|
||||
import { forwardRef, memo, useCallback } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import MaterialIcon from '@/shared/components/material-icon'
|
||||
import { useDetachCompileContext as useCompileContext } from '@/shared/context/detach-compile-context'
|
||||
@@ -7,7 +7,7 @@ import OLTooltip from '@/shared/components/ol/ol-tooltip'
|
||||
import OLButton from '@/shared/components/ol/ol-button'
|
||||
import OLBadge from '@/shared/components/ol/ol-badge'
|
||||
|
||||
function PdfHybridLogsButton() {
|
||||
const PdfHybridLogsButton = forwardRef<HTMLButtonElement>((_, ref) => {
|
||||
const { error, logEntries, toggleLogs, showLogs, stoppedOnFirstError } =
|
||||
useCompileContext()
|
||||
|
||||
@@ -32,6 +32,7 @@ function PdfHybridLogsButton() {
|
||||
overlayProps={{ placement: 'bottom' }}
|
||||
>
|
||||
<OLButton
|
||||
ref={ref}
|
||||
variant="link"
|
||||
disabled={Boolean(error || stoppedOnFirstError)}
|
||||
active={showLogs}
|
||||
@@ -50,6 +51,8 @@ function PdfHybridLogsButton() {
|
||||
</OLButton>
|
||||
</OLTooltip>
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
PdfHybridLogsButton.displayName = 'PdfHybridLogsButton'
|
||||
|
||||
export default memo(PdfHybridLogsButton)
|
||||
|
||||
@@ -19,7 +19,7 @@ const useTutorial = (
|
||||
action = 'complete',
|
||||
...rest
|
||||
}: {
|
||||
event: 'promo-click' | 'promo-dismiss'
|
||||
event: string
|
||||
action: 'complete' | 'postpone'
|
||||
} & Record<string, any>) => {
|
||||
eventTracking.sendMB(event, { ...eventData, ...rest })
|
||||
@@ -34,12 +34,15 @@ const useTutorial = (
|
||||
[deactivateTutorial, eventData, tutorialKey]
|
||||
)
|
||||
|
||||
const dismissTutorial = useCallback(async () => {
|
||||
await completeTutorial({
|
||||
event: 'promo-dismiss',
|
||||
action: 'complete',
|
||||
})
|
||||
}, [completeTutorial])
|
||||
const dismissTutorial = useCallback(
|
||||
async (eventName: string = 'promo-dismiss') => {
|
||||
await completeTutorial({
|
||||
event: eventName,
|
||||
action: 'complete',
|
||||
})
|
||||
},
|
||||
[completeTutorial]
|
||||
)
|
||||
|
||||
const maybeLater = useCallback(async () => {
|
||||
await completeTutorial({
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
@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';
|
||||
@@ -36,6 +35,8 @@
|
||||
@import 'editor/math-preview';
|
||||
@import 'editor/references-search';
|
||||
@import 'editor/editor-survey';
|
||||
@import 'editor/editor-tour-tooltip';
|
||||
@import 'editor/new-editor-promo-modal';
|
||||
@import 'error-pages';
|
||||
@import 'website-redesign';
|
||||
@import 'group-settings';
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
.editor-tour-tooltip {
|
||||
--bs-popover-bg: var(--bg-light-primary);
|
||||
--bs-popover-header-bg: var(--bg-light-primary);
|
||||
--bs-popover-body-color: var(--content-primary);
|
||||
--bs-popover-header-color: var(--content-primary);
|
||||
--editor-tour-tooltip-link-color: var(--link-ui);
|
||||
--editor-tour-tooltip-link-hover-color: var(--link-ui-hover);
|
||||
--editor-tour-tooltip-link-visited-color: var(--link-ui-visited);
|
||||
|
||||
a {
|
||||
color: var(--editor-tour-tooltip-link-color);
|
||||
|
||||
&:visited {
|
||||
color: var(--editor-tour-tooltip-link-hover-color);
|
||||
}
|
||||
|
||||
&:hover {
|
||||
color: var(--editor-tour-tooltip-link-visited-color);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@include theme('light') {
|
||||
.editor-tour-tooltip {
|
||||
--bs-popover-bg: var(--bg-dark-primary);
|
||||
--bs-popover-header-bg: var(--bg-dark-primary);
|
||||
--bs-popover-body-color: var(--content-primary-dark);
|
||||
--bs-popover-header-color: var(--content-primary-dark);
|
||||
--editor-tour-tooltip-link-color: var(--link-ui-dark);
|
||||
--editor-tour-tooltip-link-hover-color: var(--link-ui-hover-dark);
|
||||
--editor-tour-tooltip-link-visited-color: var(--link-ui-visited-dark);
|
||||
}
|
||||
}
|
||||
|
||||
.editor-tour-tooltip-footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
|
||||
.btn-link {
|
||||
color: var(--bs-popover-body-color);
|
||||
}
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
.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;
|
||||
|
||||
hr {
|
||||
margin: var(--spacing-04) 0;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
ul {
|
||||
margin-bottom: 0;
|
||||
|
||||
li:not(:last-child) {
|
||||
margin-bottom: var(--spacing-04);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
.new-editor-promo-modal-body,
|
||||
.new-editor-intro-modal-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--spacing-04);
|
||||
|
||||
video {
|
||||
border-radius: var(--border-radius-base);
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@@ -98,18 +98,3 @@
|
||||
justify-content: flex-end;
|
||||
margin-left: var(--spacing-06);
|
||||
}
|
||||
|
||||
.ide-setting-new-editor {
|
||||
display: flex;
|
||||
gap: var(--spacing-04);
|
||||
}
|
||||
|
||||
.ide-setting-beta-tag {
|
||||
font-size: var(--font-size-01);
|
||||
line-height: var(--line-height-01);
|
||||
color: var(--green-60);
|
||||
background: var(--bg-accent-03);
|
||||
border: 1px solid var(--green-50);
|
||||
border-radius: var(--border-radius-full);
|
||||
padding: var(--spacing-01) var(--spacing-03);
|
||||
}
|
||||
|
||||
@@ -540,3 +540,9 @@
|
||||
font-size: var(--font-size-01);
|
||||
margin-right: var(--spacing-04);
|
||||
}
|
||||
|
||||
.try-new-editor-button {
|
||||
.button-content {
|
||||
gap: var(--spacing-02);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,6 +233,7 @@
|
||||
"basic": "Basic",
|
||||
"basic_compile_time": "Basic compile time",
|
||||
"basic_compile_timeout_on_fast_servers": "Basic compile timeout on fast servers",
|
||||
"be_one_of_the_first_to_try_out_the_new_and_improved_overleaf_editor": "Be one of the first to try out the improved __appName__ editor design, bringing you a cleaner, less cluttered interface to help you focus on what matters—your work.",
|
||||
"before_you_use_error_assistant": "Before you use Error Assist",
|
||||
"beta": "Beta",
|
||||
"beta_feature_badge": "Beta feature badge",
|
||||
@@ -242,7 +243,6 @@
|
||||
"beta_program_not_participating": "You are not enrolled in the beta program",
|
||||
"beta_program_opt_in_action": "Opt-in to beta program",
|
||||
"beta_program_opt_out_action": "Opt-out of beta program",
|
||||
"beta_program_the_new_overleaf_editor": "Beta program: the new Overleaf editor",
|
||||
"bibliographies": "Bibliographies",
|
||||
"billed_annually_at": "Billed annually at <0>__price__</0> <1>(includes plan and any add-ons)</1>",
|
||||
"billed_monthly_at": "Billed monthly at <0>__price__</0> <1>(includes plan and any add-ons)</1>",
|
||||
@@ -307,6 +307,7 @@
|
||||
"certificate": "Certificate",
|
||||
"change": "Change",
|
||||
"change_currency": "Change currency",
|
||||
"change_how_you_see_the_editor": "Change how you see the editor using the updated <strong>Appearance</strong> settings.",
|
||||
"change_language": "Change language",
|
||||
"change_or_cancel-cancel": "cancel",
|
||||
"change_or_cancel-change": "Change",
|
||||
@@ -749,6 +750,7 @@
|
||||
"expires_on": "Expires: __date__",
|
||||
"expiry": "Expiry Date",
|
||||
"explore_all_plans": "Explore all plans",
|
||||
"explore_what_s_new": "Explore what’s new",
|
||||
"export_csv": "Export CSV",
|
||||
"export_project_to_github": "Export Project to GitHub",
|
||||
"failed_to_send_group_invite_to_email": "Failed to send Group invite to <0>__email__</0>. Please try again later.",
|
||||
@@ -792,11 +794,13 @@
|
||||
"filter_projects": "Filter projects",
|
||||
"filters": "Filters",
|
||||
"find": "Find",
|
||||
"find_and_fix_errors_faster": "Find and fix errors faster",
|
||||
"find_out_more": "Find out More",
|
||||
"find_out_more_about_institution_login": "Find out more about institutional login",
|
||||
"find_out_more_about_the_file_outline": "Find out more about the file outline",
|
||||
"find_out_more_nt": "Find out more.",
|
||||
"finding_a_fix": "Finding a fix",
|
||||
"finish": "Finish",
|
||||
"first_name": "First name",
|
||||
"fit_to_height": "Fit to height",
|
||||
"fit_to_width": "Fit to width",
|
||||
@@ -1072,7 +1076,6 @@
|
||||
"imported_from_zotero_at_date": "Imported from Zotero at __formattedDate__ __relativeDate__",
|
||||
"importing": "Importing",
|
||||
"importing_and_merging_changes_in_github": "Importing and merging changes in GitHub",
|
||||
"improved_dark_mode": "Improved dark mode",
|
||||
"in_order_to_have_a_secure_account_make_sure_your_password": "To help keep your account secure, make sure your new password:",
|
||||
"in_order_to_match_institutional_metadata_2": "In order to match your institutional metadata, we’ve linked your account using <0>__email__</0>.",
|
||||
"in_order_to_match_institutional_metadata_associated": "In order to match your institutional metadata, your account is associated with the email <b>__email__</b>.",
|
||||
@@ -1123,6 +1126,7 @@
|
||||
"integrations": "Integrations",
|
||||
"integrations_like_github": "Integrations like GitHub Sync",
|
||||
"interested_in_cheaper_personal_plan": "Would you be interested in the cheaper <0>__price__</0> Personal plan?",
|
||||
"introducing_overleafs_new_look": "Introducing __appName__’s new look",
|
||||
"invalid_certificate": "Invalid certificate. Please check the certificate and try again.",
|
||||
"invalid_confirmation_code": "That didn’t work. Please check the code and try again.",
|
||||
"invalid_email": "An email address is invalid",
|
||||
@@ -1438,15 +1442,11 @@
|
||||
"new_compile_domain_notice": "We’ve recently migrated PDF downloads to a new domain. Something might be blocking your browser from accessing that new domain, <0>__compilesUserContentDomain__</0>. This could be caused by network blocking or a strict browser plugin rule. Please follow our <1>troubleshooting guide</1>.",
|
||||
"new_compiles_in_this_project_will_automatically_use_the_newest_version": "New compiles in this project will automatically use the newest version. <0>Learn how to change compiler settings</0>",
|
||||
"new_create_tables_and_equations": "NEW! <sparkle/> Create tables and equations in seconds",
|
||||
"new_editor": "New editor",
|
||||
"new_editor_experience": "New editor experience",
|
||||
"new_editor_info": "Our new editor is currently in beta. Disabling this option will change your experience to the old Overleaf editor.",
|
||||
"new_editor_look": "New editor look",
|
||||
"new_error_logs_make_it_easier_to_find_whats_wrong": "New error logs make it easier to find what’s wrong and fix your document, so you can get compiling again.",
|
||||
"new_file": "New file",
|
||||
"new_folder": "New folder",
|
||||
"new_look_and_feel": "New look and feel",
|
||||
"new_look_and_placement_of_the_settings": "New look and placement of the settings",
|
||||
"new_name": "New name",
|
||||
"new_navigation_introducing_left_hand_side_rail_and_top_menus": "New navigation - introducing left-hand side rail and top menus",
|
||||
"new_password": "New password",
|
||||
"new_project": "New project",
|
||||
"new_snippet_project": "Untitled",
|
||||
@@ -1507,6 +1507,7 @@
|
||||
"not_managed": "Not managed",
|
||||
"not_now": "Not now",
|
||||
"not_registered": "Not registered",
|
||||
"not_sure_about_switching_yet": "Not sure about switching yet?",
|
||||
"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 Overleaf’s Professional features.",
|
||||
@@ -1588,6 +1589,7 @@
|
||||
"overleaf_plans_and_pricing": "overleaf plans and pricing",
|
||||
"overleaf_template_gallery": "overleaf template gallery",
|
||||
"overleafs_functionality_meets_my_needs": "Overleaf’s functionality meets my needs.",
|
||||
"overleafs_new_look_is_here": "__appName__’s new look is here",
|
||||
"overview": "Overview",
|
||||
"overwrite": "Overwrite",
|
||||
"overwriting_the_original_folder": "Overwriting the original folder will delete it and all the files it contains.",
|
||||
@@ -1809,6 +1811,7 @@
|
||||
"read_lines_from_path": "Read lines from __path__",
|
||||
"read_more": "Read more",
|
||||
"read_more_about_managed_users": "Read more about managed users",
|
||||
"read_more_about_the_new_editor": "<0>Read more about the new editor design</0>, or temporarily switch back to the old editor using the <1>Appearance</1> settings.",
|
||||
"read_only_dropbox_sync_message": "As a read-only viewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not</0> sync back to Overleaf.",
|
||||
"read_only_token": "Read-Only Token",
|
||||
"read_write_token": "Read-Write Token",
|
||||
@@ -1951,7 +1954,6 @@
|
||||
"revert_pending_plan_change": "Revert scheduled plan change",
|
||||
"review": "Review",
|
||||
"review_panel": "Review panel",
|
||||
"review_panel_and_error_logs_moved_to_the_left": "Review panel and error logs moved to the left",
|
||||
"reviewer": "Reviewer",
|
||||
"reviewer_dropbox_sync_message": "As a reviewer you can sync the current project version to Dropbox, but changes made in Dropbox will <0>not</0> sync back to Overleaf.",
|
||||
"reviewing": "Reviewing",
|
||||
@@ -2085,6 +2087,7 @@
|
||||
"setup_another_account_under_a_personal_email_address": "Set up another Overleaf account under a personal email address.",
|
||||
"share": "Share",
|
||||
"share_feedback": "Share feedback",
|
||||
"share_feedback_on_the_new_editor": "Share feedback on the new editor look.",
|
||||
"share_project": "Share Project",
|
||||
"shared_with_you": "Shared with you",
|
||||
"sharelatex_beta_program": "__appName__ beta program",
|
||||
@@ -2114,6 +2117,7 @@
|
||||
"sign_up_for_free": "Sign up for free",
|
||||
"sign_up_for_free_account": "Sign up for a free account and receive regular updates",
|
||||
"simple_search_mode": "Simple search",
|
||||
"simplified_working_starts_here": "Simplified working starts here",
|
||||
"single_sign_on_sso": "Single Sign-On (SSO)",
|
||||
"site_description": "An online LaTeX editor that’s easy to use. No installation, real-time collaboration, version control, hundreds of LaTeX templates, and more.",
|
||||
"site_wide_option_available": "Site-wide option available",
|
||||
@@ -2267,10 +2271,10 @@
|
||||
"sure_you_want_to_delete": "Are you sure you want to permanently delete the following files?",
|
||||
"sure_you_want_to_leave_group": "Are you sure you want to leave this group?",
|
||||
"sv": "Swedish",
|
||||
"switch_between_dark_and_light_mode": "Switch between dark and light mode",
|
||||
"switch_compile_mode_for_faster_draft_compilation": "Switch compile mode for faster draft compilation",
|
||||
"switch_easily_between_your_files_comments_track_changes_and_more": "Switch easily between your files, comments, track changes, and more in the new left-hand menu.",
|
||||
"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_personal_email_to_keep_your_accounts_separate": "Switch to a personal email to keep your accounts separate.",
|
||||
"switch_to_standard_plan": "Switch to Standard plan",
|
||||
@@ -2346,8 +2350,7 @@
|
||||
"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_latex_engine_used_for_compiling": "The LaTeX engine used for compiling",
|
||||
"the_new_overleaf_editor_try_now_in_beta": "The new Overleaf editor — try now in beta",
|
||||
"the_next_payment_will_be_collected_on": "The next payment will be collected on <strong>__date__</strong>.",
|
||||
"the_new_overleaf_editor_info": "__appName__’s new look is here. Disabling this option will switch you back to the old editor design.",
|
||||
"the_original_text_has_changed": "The original text has changed, so this suggestion can’t be applied",
|
||||
"the_overleaf_color_scheme": "The __appName__ color scheme",
|
||||
"the_primary_file_for_compiling_your_project": "The primary file for compiling your project",
|
||||
@@ -2385,7 +2388,6 @@
|
||||
"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_beta_release_for_the_new_overleaf_editor": "This is a beta release for the new Overleaf editor. 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",
|
||||
@@ -2520,10 +2522,12 @@
|
||||
"try_for_free": "Try for free",
|
||||
"try_it_for_free": "Try it for free",
|
||||
"try_now": "Try Now",
|
||||
"try_out_the_new_editor_now": "Try out the new design now (you can switch back at any time), or <0>read more about the changes we’re making</0>.",
|
||||
"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 guide</0>.",
|
||||
"try_relinking_provider": "It looks like you need to re-link your __provider__ account.",
|
||||
"try_the_new_editor": "Try the new editor",
|
||||
"try_the_new_editor_design": "Try the new editor design",
|
||||
"try_the_new_look": "Try the new look",
|
||||
"try_to_compile_despite_errors": "Try to compile despite errors",
|
||||
"turn_off": "Turn off",
|
||||
"turn_off_link_sharing": "Turn off link sharing",
|
||||
@@ -2701,14 +2705,13 @@
|
||||
"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 sharing</0>. 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.",
|
||||
"weve_redesigned_our_editor_to_make_it_easier_to_use_and_future_ready": "We’ve redesigned our editor to make it easier to use and future ready. It’s now in beta, so try it out and give us your feedback.",
|
||||
"weve_made_it_easier_to_find_and_use_the_tools_you_need_today": "The new editor design makes it easier to find and use the tools you need today, while making space for the new features you’ll love tomorrow.",
|
||||
"what_did_you_find_most_helpful": "What did you find most helpful?",
|
||||
"what_do_you_need": "What do you need?",
|
||||
"what_do_you_need_help_with": "What do you need help with?",
|
||||
"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_different": "What’s different?",
|
||||
"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?",
|
||||
@@ -2766,7 +2769,6 @@
|
||||
"you_can_select_or_invite_collaborator": "You can select or invite __count__ collaborator on your current plan. Upgrade to add more editors or reviewers.",
|
||||
"you_can_select_or_invite_collaborator_plural": "You can select or invite __count__ collaborators on your current plan. Upgrade to add more editors or reviewers.",
|
||||
"you_can_still_use_your_premium_features": "You can still use your premium features until the pause becomes active.",
|
||||
"you_can_switch_back_to_the_old_editor_at_any_time": "You can switch back to the old editor at any time.",
|
||||
"you_cant_add_or_change_password_due_to_sso": "You can’t add or change your password because your group or organization uses <0>single sign-on (SSO)</0>.",
|
||||
"you_cant_join_this_group_subscription": "You can’t join this group subscription",
|
||||
"you_cant_reset_password_due_to_sso": "You can’t reset your password because your group or organization uses SSO. <0>Log in with SSO</0>.",
|
||||
|
||||
Reference in New Issue
Block a user