mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-11-13 21:53:43 +01:00
Merge pull request #32642 from overleaf/ab-non-blinking-cursor-editor-setting
[web] Add non-blinking cursor editor setting GitOrigin-RevId: 078efca21c1e87905894aadc1430c1728a38685d
This commit is contained in:
committed by
Copybot
parent
3f62eac181
commit
50db96878d
@@ -44,6 +44,7 @@ async function buildUserSettings(req, res, user) {
|
||||
overallTheme: await getOverallTheme(req, res, user),
|
||||
mathPreview: user.ace.mathPreview,
|
||||
breadcrumbs: user.ace.breadcrumbs,
|
||||
nonBlinkingCursor: user.ace.nonBlinkingCursor ?? false,
|
||||
referencesSearchMode: user.ace.referencesSearchMode,
|
||||
darkModePdf: user.ace.darkModePdf ?? false,
|
||||
zotero: user.ace.zotero,
|
||||
|
||||
@@ -421,6 +421,9 @@ async function updateUserSettings(req, res, next) {
|
||||
if (body.breadcrumbs != null) {
|
||||
user.ace.breadcrumbs = Boolean(body.breadcrumbs)
|
||||
}
|
||||
if (body.nonBlinkingCursor != null) {
|
||||
user.ace.nonBlinkingCursor = Boolean(body.nonBlinkingCursor)
|
||||
}
|
||||
if (body.referencesSearchMode != null) {
|
||||
const mode = body.referencesSearchMode === 'simple' ? 'simple' : 'advanced'
|
||||
user.ace.referencesSearchMode = mode
|
||||
|
||||
@@ -106,6 +106,7 @@ export const UserSchema = new Schema(
|
||||
lineHeight: { type: String },
|
||||
mathPreview: { type: Boolean, default: true },
|
||||
breadcrumbs: { type: Boolean, default: true },
|
||||
nonBlinkingCursor: { type: Boolean, default: false },
|
||||
referencesSearchMode: { type: String, default: 'advanced' }, // 'advanced' or 'simple'
|
||||
darkModePdf: { type: Boolean, default: false },
|
||||
zotero: refProviderSettingsSchema,
|
||||
|
||||
@@ -1231,6 +1231,7 @@
|
||||
"no_symbols_found": "",
|
||||
"no_thanks_cancel_now": "",
|
||||
"no_token": "",
|
||||
"non_blinking_cursor": "",
|
||||
"normal": "",
|
||||
"normally_x_price_per_month": "",
|
||||
"normally_x_price_per_year": "",
|
||||
@@ -1497,6 +1498,7 @@
|
||||
"redirect_url": "",
|
||||
"redo": "",
|
||||
"reduce_costs_group_licenses": "",
|
||||
"reduces_visual_distraction_by_keeping_the_cursor_solid": "",
|
||||
"reference_error_relink_hint": "",
|
||||
"reference_manager_linked": "",
|
||||
"reference_manager_lowercase": "",
|
||||
|
||||
+9
@@ -33,6 +33,9 @@ type ProjectSettingsSetterContextValue = {
|
||||
setPdfViewer: (pdfViewer: UserSettings['pdfViewer']) => void
|
||||
setMathPreview: (mathPreview: UserSettings['mathPreview']) => void
|
||||
setBreadcrumbs: (breadcrumbs: UserSettings['breadcrumbs']) => void
|
||||
setNonBlinkingCursor: (
|
||||
nonBlinkingCursor: UserSettings['nonBlinkingCursor']
|
||||
) => void
|
||||
setDarkModePdf: (darkModePdf: UserSettings['darkModePdf']) => void
|
||||
setZotero: (zotero: UserSettings['zotero']) => void
|
||||
setMendeley: (mendeley: UserSettings['mendeley']) => void
|
||||
@@ -92,6 +95,8 @@ export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
|
||||
setMathPreview,
|
||||
breadcrumbs,
|
||||
setBreadcrumbs,
|
||||
nonBlinkingCursor,
|
||||
setNonBlinkingCursor,
|
||||
darkModePdf,
|
||||
setDarkModePdf,
|
||||
zotero,
|
||||
@@ -144,6 +149,8 @@ export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
|
||||
setMathPreview,
|
||||
breadcrumbs,
|
||||
setBreadcrumbs,
|
||||
nonBlinkingCursor,
|
||||
setNonBlinkingCursor,
|
||||
darkModePdf,
|
||||
setDarkModePdf,
|
||||
zotero,
|
||||
@@ -192,6 +199,8 @@ export const ProjectSettingsProvider: FC<React.PropsWithChildren> = ({
|
||||
setMathPreview,
|
||||
breadcrumbs,
|
||||
setBreadcrumbs,
|
||||
nonBlinkingCursor,
|
||||
setNonBlinkingCursor,
|
||||
darkModePdf,
|
||||
setDarkModePdf,
|
||||
zotero,
|
||||
|
||||
@@ -24,6 +24,7 @@ export default function useUserWideSettings() {
|
||||
pdfViewer,
|
||||
mathPreview,
|
||||
breadcrumbs,
|
||||
nonBlinkingCursor,
|
||||
darkModePdf,
|
||||
zotero,
|
||||
mendeley,
|
||||
@@ -129,6 +130,13 @@ export default function useUserWideSettings() {
|
||||
[saveUserSettings]
|
||||
)
|
||||
|
||||
const setNonBlinkingCursor = useCallback(
|
||||
(nonBlinkingCursor: UserSettings['nonBlinkingCursor']) => {
|
||||
saveUserSettings('nonBlinkingCursor', nonBlinkingCursor)
|
||||
},
|
||||
[saveUserSettings]
|
||||
)
|
||||
|
||||
const setDarkModePdf = useCallback(
|
||||
(darkModePdf: UserSettings['darkModePdf']) => {
|
||||
saveUserSettings('darkModePdf', darkModePdf)
|
||||
@@ -188,6 +196,8 @@ export default function useUserWideSettings() {
|
||||
setMathPreview,
|
||||
breadcrumbs,
|
||||
setBreadcrumbs,
|
||||
nonBlinkingCursor,
|
||||
setNonBlinkingCursor,
|
||||
darkModePdf,
|
||||
setDarkModePdf,
|
||||
zotero,
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
import { useProjectSettingsContext } from '@/features/editor-left-menu/context/project-settings-context'
|
||||
import ToggleSetting from '../toggle-setting'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
|
||||
export default function NonBlinkingCursorSetting() {
|
||||
const { nonBlinkingCursor, setNonBlinkingCursor } =
|
||||
useProjectSettingsContext()
|
||||
const { t } = useTranslation()
|
||||
|
||||
return (
|
||||
<ToggleSetting
|
||||
id="non-blinking-cursor"
|
||||
label={t('non_blinking_cursor')}
|
||||
description={t('reduces_visual_distraction_by_keeping_the_cursor_solid')}
|
||||
checked={nonBlinkingCursor}
|
||||
onChange={setNonBlinkingCursor}
|
||||
/>
|
||||
)
|
||||
}
|
||||
@@ -11,6 +11,7 @@ import SpellCheckSetting from '@/features/settings/components/editor-settings/sp
|
||||
import DictionarySetting from '@/features/settings/components/editor-settings/dictionary-setting'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import BreadcrumbsSetting from '@/features/settings/components/editor-settings/breadcrumbs-setting'
|
||||
import NonBlinkingCursorSetting from '@/features/settings/components/editor-settings/non-blinking-cursor-setting'
|
||||
import MathPreviewSetting from '@/features/settings/components/editor-settings/math-preview-setting'
|
||||
import RootDocumentSetting from '@/features/settings/components/compiler-settings/root-document-setting'
|
||||
import CompilerSetting from '@/features/settings/components/compiler-settings/compiler-setting'
|
||||
@@ -111,6 +112,10 @@ export const SettingsModalProvider: FC<React.PropsWithChildren> = ({
|
||||
key: 'autoPairDelimiters',
|
||||
component: <AutoCloseBracketsSetting />,
|
||||
},
|
||||
{
|
||||
key: 'nonBlinkingCursor',
|
||||
component: <NonBlinkingCursorSetting />,
|
||||
},
|
||||
{
|
||||
key: 'syntaxValidation',
|
||||
component: <CodeCheckSetting />,
|
||||
|
||||
@@ -9,6 +9,8 @@ import browser from './browser'
|
||||
* copied to make use of a custom version of rectangleMarkerForRange which calls
|
||||
* fullHeightCoordsAtPos when in Source mode, extending the top and bottom
|
||||
* of the coords to cover the full line height.
|
||||
*
|
||||
* Whenever possible, we should avoid making changes to this extension.
|
||||
*/
|
||||
export const drawSelection = () => {
|
||||
return [cursorLayer, selectionLayer, Prec.highest(hideNativeSelection)]
|
||||
|
||||
@@ -35,6 +35,7 @@ import importOverleafModules from '../../../../macros/import-overleaf-module.mac
|
||||
import { emptyLineFiller } from './empty-line-filler'
|
||||
import { goToLinePanel } from './go-to-line'
|
||||
import { drawSelection } from './draw-selection'
|
||||
import { nonBlinkingCursor } from './non-blinking-cursor'
|
||||
import { sourceOnly, visual } from './visual/visual'
|
||||
import { inlineBackground } from './inline-background'
|
||||
import { indentationMarkers } from './indentation-markers'
|
||||
@@ -75,6 +76,7 @@ export const createExtensions = (options: Record<string, any>): Extension[] => [
|
||||
closedText: '▸',
|
||||
}),
|
||||
drawSelection(),
|
||||
nonBlinkingCursor(),
|
||||
// A built-in facet that is set to true to allow multiple selections.
|
||||
// This makes the editor more like a code editor than Google Docs or Microsoft Word,
|
||||
// which only have single selections.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import { StateEffect, StateField, TransactionSpec } from '@codemirror/state'
|
||||
import { EditorView } from '@codemirror/view'
|
||||
|
||||
const nonBlinkingCursorEffect = StateEffect.define<boolean>()
|
||||
|
||||
const nonBlinkingCursorTheme = EditorView.baseTheme({
|
||||
'&.cm-non-blinking-cursor .cm-cursorLayer': {
|
||||
animationName: 'none !important',
|
||||
},
|
||||
})
|
||||
|
||||
const nonBlinkingCursorField = StateField.define<boolean>({
|
||||
create: () => false,
|
||||
update: (value, tr) => {
|
||||
for (const effect of tr.effects) {
|
||||
if (effect.is(nonBlinkingCursorEffect)) {
|
||||
value = effect.value
|
||||
}
|
||||
}
|
||||
return value
|
||||
},
|
||||
provide(field) {
|
||||
return [
|
||||
EditorView.editorAttributes.from(field, field => ({
|
||||
class: field ? 'cm-non-blinking-cursor' : '',
|
||||
})),
|
||||
nonBlinkingCursorTheme,
|
||||
]
|
||||
},
|
||||
})
|
||||
|
||||
export const setNonBlinkingCursor = (enabled: boolean): TransactionSpec => ({
|
||||
effects: nonBlinkingCursorEffect.of(enabled),
|
||||
})
|
||||
|
||||
export const nonBlinkingCursor = () => nonBlinkingCursorField
|
||||
@@ -44,6 +44,7 @@ import { useMetadataContext } from '@/features/ide-react/context/metadata-contex
|
||||
import { useUserContext } from '@/shared/context/user-context'
|
||||
import { useReferencesContext } from '@/features/ide-react/context/references-context'
|
||||
import { setMathPreview } from '@/features/source-editor/extensions/math-preview'
|
||||
import { setNonBlinkingCursor } from '@/features/source-editor/extensions/non-blinking-cursor'
|
||||
import { useRangesContext } from '@/features/review-panel/context/ranges-context'
|
||||
import { updateRanges } from '@/features/source-editor/extensions/ranges'
|
||||
import { useThreadsContext } from '@/features/review-panel/context/threads-context'
|
||||
@@ -88,6 +89,7 @@ function useCodeMirrorScope(view: EditorView) {
|
||||
mode,
|
||||
syntaxValidation,
|
||||
mathPreview,
|
||||
nonBlinkingCursor,
|
||||
referencesSearchMode,
|
||||
} = userSettings
|
||||
const activeOverallTheme = useActiveOverallTheme()
|
||||
@@ -158,6 +160,7 @@ function useCodeMirrorScope(view: EditorView) {
|
||||
mode,
|
||||
syntaxValidation,
|
||||
mathPreview,
|
||||
nonBlinkingCursor,
|
||||
referencesSearchMode,
|
||||
})
|
||||
|
||||
@@ -467,6 +470,13 @@ function useCodeMirrorScope(view: EditorView) {
|
||||
})
|
||||
}, [view, mathPreview])
|
||||
|
||||
useEffect(() => {
|
||||
settingsRef.current.nonBlinkingCursor = nonBlinkingCursor
|
||||
window.setTimeout(() => {
|
||||
view.dispatch(setNonBlinkingCursor(nonBlinkingCursor))
|
||||
})
|
||||
}, [view, nonBlinkingCursor])
|
||||
|
||||
useEffect(() => {
|
||||
settingsRef.current.referencesSearchMode = referencesSearchMode
|
||||
}, [referencesSearchMode])
|
||||
|
||||
@@ -31,6 +31,7 @@ export const defaultSettings: UserSettings = {
|
||||
mathPreview: true,
|
||||
referencesSearchMode: 'advanced',
|
||||
breadcrumbs: true,
|
||||
nonBlinkingCursor: false,
|
||||
darkModePdf: false,
|
||||
zotero: {
|
||||
enabled: true,
|
||||
|
||||
@@ -1594,6 +1594,7 @@
|
||||
"no_thanks_cancel_now": "No thanks, I still want to cancel",
|
||||
"no_token": "No token",
|
||||
"no_update_email": "No, update email",
|
||||
"non_blinking_cursor": "Non-blinking cursor",
|
||||
"non_deletable_entity": "The specified entity may not be deleted",
|
||||
"normal": "Normal",
|
||||
"normally_x_price_per_month": "Normally __price__ per month",
|
||||
@@ -1952,6 +1953,7 @@
|
||||
"redirecting": "Redirecting",
|
||||
"redo": "Redo",
|
||||
"reduce_costs_group_licenses": "You can cut down on paperwork and reduce costs with our discounted group licenses.",
|
||||
"reduces_visual_distraction_by_keeping_the_cursor_solid": "Reduces visual distraction by keeping the cursor solid",
|
||||
"refer_to_doc_link": "If needed, you can refer to the <a href=\"__link__\">Overleaf API documentation</a>.",
|
||||
"reference_error_relink_hint": "If this error persists, try re-linking your account here:",
|
||||
"reference_manager_linked": "Reference manager linked",
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
import { screen, render } from '@testing-library/react'
|
||||
import { expect } from 'chai'
|
||||
import fetchMock from 'fetch-mock'
|
||||
import { SettingsModalProvider } from '@/features/settings/context/settings-modal-context'
|
||||
import { EditorProviders } from '../../../helpers/editor-providers'
|
||||
import NonBlinkingCursorSetting from '@/features/settings/components/editor-settings/non-blinking-cursor-setting'
|
||||
|
||||
describe('<NonBlinkingCursorSetting />', function () {
|
||||
afterEach(function () {
|
||||
fetchMock.removeRoutes().clearHistory()
|
||||
})
|
||||
|
||||
it('can toggle', async function () {
|
||||
render(
|
||||
<EditorProviders>
|
||||
<SettingsModalProvider>
|
||||
<NonBlinkingCursorSetting />
|
||||
</SettingsModalProvider>
|
||||
</EditorProviders>
|
||||
)
|
||||
|
||||
const saveSettingsMock = fetchMock.post(
|
||||
`express:/user/settings`,
|
||||
{
|
||||
status: 200,
|
||||
},
|
||||
{ delay: 0 }
|
||||
)
|
||||
|
||||
const toggle = screen.getByLabelText('Non-blinking cursor')
|
||||
const startingCheckedValue = (toggle as HTMLInputElement).checked
|
||||
|
||||
// Toggle the checkbox
|
||||
toggle.click()
|
||||
expect((toggle as HTMLInputElement).checked).to.equal(!startingCheckedValue)
|
||||
expect(
|
||||
saveSettingsMock.callHistory.called(`/user/settings`, {
|
||||
body: { nonBlinkingCursor: !startingCheckedValue },
|
||||
})
|
||||
).to.be.true
|
||||
|
||||
// Toggle back to original value
|
||||
toggle.click()
|
||||
expect((toggle as HTMLInputElement).checked).to.equal(startingCheckedValue)
|
||||
expect(
|
||||
saveSettingsMock.callHistory.called(`/user/settings`, {
|
||||
body: { nonBlinkingCursor: startingCheckedValue },
|
||||
})
|
||||
).to.be.true
|
||||
})
|
||||
})
|
||||
@@ -27,6 +27,7 @@ export type UserSettings = {
|
||||
mathPreview: boolean
|
||||
referencesSearchMode: 'advanced' | 'simple'
|
||||
breadcrumbs: boolean
|
||||
nonBlinkingCursor: boolean
|
||||
darkModePdf: boolean
|
||||
zotero: RefProviderSettings
|
||||
mendeley: RefProviderSettings
|
||||
|
||||
Reference in New Issue
Block a user