Display current spell check language and option to change it (#21138)

GitOrigin-RevId: 87cf140a7e3e719125eb6d2df23d6c6bd6631fe8
This commit is contained in:
Alf Eaton 2024-10-17 14:52:58 +01:00 committed by Copybot
parent b5015b82c2
commit 1b2f5af1c0
14 changed files with 161 additions and 15 deletions

View file

@ -183,6 +183,7 @@
"center": "",
"change": "",
"change_currency": "",
"change_language": "",
"change_or_cancel-cancel": "",
"change_or_cancel-change": "",
"change_or_cancel-or": "",

View file

@ -0,0 +1,44 @@
import { createContext, FC, useCallback, useContext, useState } from 'react'
import useEventListener from '@/shared/hooks/use-event-listener'
type EditorLeftMenuState = {
settingToFocus?: string
}
export const EditorLeftMenuContext = createContext<
EditorLeftMenuState | undefined
>(undefined)
export const EditorLeftMenuProvider: FC = ({ children }) => {
const [value, setValue] = useState<EditorLeftMenuState>(() => ({
settingToFocus: undefined,
}))
useEventListener(
'ui.focus-setting',
useCallback(event => {
setValue(value => ({
...value,
settingToFocus: (event as CustomEvent<string>).detail,
}))
}, [])
)
return (
<EditorLeftMenuContext.Provider value={value}>
{children}
</EditorLeftMenuContext.Provider>
)
}
export const useEditorLeftMenuContext = () => {
const value = useContext(EditorLeftMenuContext)
if (!value) {
throw new Error(
`useEditorLeftMenuContext is only available inside EditorLeftMenuProvider`
)
}
return value
}

View file

@ -7,6 +7,8 @@ import { lazy, memo, Suspense } from 'react'
import { FullSizeLoadingSpinner } from '@/shared/components/loading-spinner'
import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/bootstrap-version-switcher'
import { Offcanvas } from 'react-bootstrap-5'
import { EditorLeftMenuProvider } from './editor-left-menu-context'
const EditorLeftMenuBody = lazy(() => import('./editor-left-menu-body'))
function EditorLeftMenu() {
@ -19,7 +21,7 @@ function EditorLeftMenu() {
return (
<BootstrapVersionSwitcher
bs3={
<>
<EditorLeftMenuProvider>
<AccessibleModal
backdropClassName="left-menu-modal-backdrop"
keyboard
@ -37,10 +39,10 @@ function EditorLeftMenu() {
</Modal.Body>
</AccessibleModal>
{leftMenuShown && <LeftMenuMask />}
</>
</EditorLeftMenuProvider>
}
bs5={
<>
<EditorLeftMenuProvider>
<Offcanvas
show={leftMenuShown}
onHide={closeLeftMenu}
@ -59,7 +61,7 @@ function EditorLeftMenu() {
</Offcanvas.Body>
</Offcanvas>
{leftMenuShown && <LeftMenuMask />}
</>
</EditorLeftMenuProvider>
}
/>
)

View file

@ -2,8 +2,9 @@ import BootstrapVersionSwitcher from '@/features/ui/components/bootstrap-5/boots
import OLFormGroup from '@/features/ui/components/ol/ol-form-group'
import OLFormLabel from '@/features/ui/components/ol/ol-form-label'
import OLFormSelect from '@/features/ui/components/ol/ol-form-select'
import { ChangeEventHandler, useCallback } from 'react'
import { ChangeEventHandler, useCallback, useEffect, useRef } from 'react'
import { Spinner } from 'react-bootstrap-5'
import { useEditorLeftMenuContext } from '@/features/editor-left-menu/components/editor-left-menu-context'
type PossibleValue = string | number | boolean
@ -54,6 +55,25 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
[onChange, value]
)
const { settingToFocus } = useEditorLeftMenuContext()
const selectRef = useRef<HTMLSelectElement | null>(null)
useEffect(() => {
if (settingToFocus === name && selectRef.current) {
selectRef.current.scrollIntoView({
block: 'center',
behavior: 'smooth',
})
selectRef.current.focus()
}
// clear the focus setting
window.dispatchEvent(
new CustomEvent('ui.focus-setting', { detail: undefined })
)
}, [name, settingToFocus])
return (
<OLFormGroup
controlId={`settings-menu-${name}`}
@ -84,6 +104,7 @@ export default function SettingsMenuSelect<T extends PossibleValue = string>({
onChange={handleChange}
value={value?.toString()}
disabled={disabled}
ref={selectRef}
>
{options.map(option => (
<option

View file

@ -167,11 +167,13 @@ const createSpellingSuggestionList = (word: Word) => (view: EditorView) => {
word={word}
spellCheckLanguage={getSpellCheckLanguage(view.state)}
spellChecker={getSpellChecker(view.state)}
handleClose={() => {
handleClose={(focus = true) => {
view.dispatch({
effects: hideSpellingMenu.of(null),
})
if (focus) {
view.focus()
}
}}
handleLearnWord={() => {
learnWordRequest(word)

View file

@ -39,7 +39,12 @@ const SpellingSuggestionsFeedback: FC = () => {
rel="noopener noreferrer"
>
<BootstrapVersionSwitcher
bs3={<span className={classnames('badge', badgeClass)} />}
bs3={
<span
className={classnames('badge', badgeClass)}
style={{ width: 14, height: 14 }}
/>
}
bs5={
<MaterialIcon
type="info"

View file

@ -0,0 +1,39 @@
import { memo, useCallback } from 'react'
import Icon from '@/shared/components/icon'
import { useTranslation } from 'react-i18next'
import OLTooltip from '@/features/ui/components/ol/ol-tooltip'
export const SpellingSuggestionsLanguage = memo<{
language: { name: string }
handleClose: (focus: boolean) => void
}>(({ language, handleClose }) => {
const { t } = useTranslation()
const handleClick = useCallback(() => {
// open the left menu
window.dispatchEvent(
new CustomEvent('ui.toggle-left-menu', { detail: true })
)
// focus the spell check setting
window.dispatchEvent(
new CustomEvent('ui.focus-setting', { detail: 'spellCheckLanguage' })
)
handleClose(false)
}, [handleClose])
return (
<OLTooltip
id="spell-check-client-tooltip"
description={t('change_language')}
overlayProps={{ placement: 'right', delay: 100 }}
>
<button
className="btn-link text-left dropdown-menu-button"
onClick={handleClick}
>
<Icon type="cog" /> <span className="mx-1">{language.name}</span>
</button>
</OLTooltip>
)
})
SpellingSuggestionsLanguage.displayName = 'SpellingSuggestionsLanguage'

View file

@ -13,6 +13,7 @@ import classnames from 'classnames'
import { useFeatureFlag } from '@/shared/context/split-test-context'
import { sendMB } from '@/infrastructure/event-tracking'
import SpellingSuggestionsFeedback from './spelling-suggestions-feedback'
import { SpellingSuggestionsLanguage } from './spelling-suggestions-language'
import { captureException } from '@/infrastructure/error-reporter'
import { debugConsole } from '@/utils/debugging'
@ -83,6 +84,10 @@ export const SpellingSuggestions: FC<{
const spellCheckClientEnabled = useFeatureFlag('spell-check-client')
if (!language) {
return null
}
return (
<ul
className={classnames('dropdown-menu', 'dropdown-menu-unpositioned', {
@ -132,10 +137,19 @@ export const SpellingSuggestions: FC<{
handleLearnWord()
}}
/>
{spellCheckClientEnabled && language?.dic && (
<li className="divider" />
<li role="menuitem">
<SpellingSuggestionsLanguage
language={language}
handleClose={handleClose}
/>
</li>
{spellCheckClientEnabled && language.dic && (
<>
<li className="divider" />
<li>
<li role="menuitem">
<SpellingSuggestionsFeedback />
</li>
</>

View file

@ -16,6 +16,7 @@ import { DetachRole } from './detach-context'
import { debugConsole } from '@/utils/debugging'
import { BinaryFile } from '@/features/file-view/types/binary-file'
import useScopeEventEmitter from '@/shared/hooks/use-scope-event-emitter'
import useEventListener from '@/shared/hooks/use-event-listener'
export type IdeLayout = 'sideBySide' | 'flat'
export type IdeView = 'editor' | 'file' | 'pdf' | 'history'
@ -106,6 +107,16 @@ export const LayoutProvider: FC = ({ children }) => {
const [leftMenuShown, setLeftMenuShown] =
useScopeValue<boolean>('ui.leftMenuShown')
useEventListener(
'ui.toggle-left-menu',
useCallback(
event => {
setLeftMenuShown((event as CustomEvent<boolean>).detail)
},
[setLeftMenuShown]
)
)
// whether to display the editor and preview side-by-side or full-width ("flat")
const [pdfLayout, setPdfLayout] = useScopeValue<IdeLayout>('ui.pdfLayout')

View file

@ -163,7 +163,8 @@
border-bottom: 0;
}
&:hover {
&:hover,
&:focus-within {
background-color: @link-color;
label {

View file

@ -15,7 +15,7 @@
overflow: hidden auto;
transition: left ease-in-out 0.5s;
font-size: var(--font-size-02);
width: 320px;
width: 340px;
&.shown {
left: 0;
@ -147,7 +147,8 @@
border-bottom: 0;
}
&:hover {
&:hover,
&:focus-within {
background-color: var(--bg-info-01);
label {

View file

@ -781,7 +781,7 @@
@content-margin-vertical: @line-height-computed;
@left-menu-width: 320px;
@left-menu-width: 340px;
@left-menu-animation-duration: 0.35s;
@toolbar-border-color: @neutral-80;

View file

@ -249,6 +249,7 @@
"certificate": "Certificate",
"change": "Change",
"change_currency": "Change currency",
"change_language": "Change language",
"change_or_cancel-cancel": "cancel",
"change_or_cancel-change": "Change",
"change_or_cancel-or": "or",

View file

@ -35,6 +35,7 @@ import { OnlineUsersProvider } from '@/features/ide-react/context/online-users-c
import { PermissionsProvider } from '@/features/ide-react/context/permissions-context'
import { ReferencesProvider } from '@/features/ide-react/context/references-context'
import { SnapshotProvider } from '@/features/ide-react/context/snapshot-context'
import { EditorLeftMenuProvider } from '@/features/editor-left-menu/components/editor-left-menu-context'
// these constants can be imported in tests instead of
// using magic strings
@ -159,6 +160,7 @@ export function EditorProviders({
DetachCompileProvider,
DetachProvider,
EditorProvider,
EditorLeftMenuProvider,
EditorManagerProvider,
SnapshotProvider,
FileTreeDataProvider,
@ -206,7 +208,9 @@ export function EditorProviders({
<Providers.OnlineUsersProvider>
<Providers.MetadataProvider>
<Providers.OutlineProvider>
<Providers.EditorLeftMenuProvider>
{children}
</Providers.EditorLeftMenuProvider>
</Providers.OutlineProvider>
</Providers.MetadataProvider>
</Providers.OnlineUsersProvider>