mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-06-22 21:08:12 +08:00
fix(client): align multifileEditor with hotkey types (#49830)
Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
This commit is contained in:
parent
01510c77d3
commit
4cd7fe03c1
@ -74,7 +74,7 @@ export interface EditorProps {
|
||||
canFocus: boolean;
|
||||
challengeFiles: ChallengeFiles;
|
||||
challengeType: number;
|
||||
containerRef: MutableRefObject<HTMLElement | undefined>;
|
||||
containerRef?: React.RefObject<HTMLElement>;
|
||||
description: string;
|
||||
dimensions?: Dimensions;
|
||||
editorRef: MutableRefObject<editor.IStandaloneCodeEditor | undefined>;
|
||||
@ -825,7 +825,7 @@ const Editor = (props: EditorProps): JSX.Element => {
|
||||
}
|
||||
|
||||
function focusOnHotkeys() {
|
||||
const currContainerRef = props.containerRef.current;
|
||||
const currContainerRef = props.containerRef?.current;
|
||||
if (currContainerRef) {
|
||||
currContainerRef.focus();
|
||||
}
|
||||
|
||||
@ -42,6 +42,7 @@ type MultifileEditorProps = Pick<
|
||||
> & {
|
||||
visibleEditors: VisibleEditors;
|
||||
};
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
visibleEditorsSelector,
|
||||
canFocusEditorSelector,
|
||||
|
||||
@ -211,7 +211,7 @@ function ShowClassic({
|
||||
const { t } = useTranslation();
|
||||
const [resizing, setResizing] = useState(false);
|
||||
const [usingKeyboardInTablist, setUsingKeyboardInTablist] = useState(false);
|
||||
const containerRef = useRef<HTMLElement>();
|
||||
const containerRef = useRef<HTMLElement>(null);
|
||||
const editorRef = useRef<editor.IStandaloneCodeEditor>();
|
||||
const instructionsPanelRef = useRef<HTMLDivElement>(null);
|
||||
const isMobile = useMediaQuery({
|
||||
@ -415,12 +415,12 @@ function ShowClassic({
|
||||
<Hotkeys
|
||||
challengeType={challengeType}
|
||||
executeChallenge={executeChallenge}
|
||||
innerRef={containerRef}
|
||||
containerRef={containerRef}
|
||||
instructionsPanelRef={instructionsPanelRef}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
usesMultifileEditor={usesMultifileEditor}
|
||||
{...(editorRef && { editorRef: editorRef })}
|
||||
editorRef={editorRef}
|
||||
>
|
||||
<LearnLayout hasEditableBoundaries={hasEditableBoundaries}>
|
||||
<Helmet title={windowTitle} />
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
// Package Utilities
|
||||
import { Button } from '@freecodecamp/react-bootstrap';
|
||||
import { graphql } from 'gatsby';
|
||||
import React, { Component, RefObject } from 'react';
|
||||
import React, { Component } from 'react';
|
||||
import Helmet from 'react-helmet';
|
||||
import type { TFunction } from 'i18next';
|
||||
import { Trans, withTranslation } from 'react-i18next';
|
||||
@ -113,7 +113,7 @@ interface ShowCodeAllyProps {
|
||||
|
||||
class ShowCodeAlly extends Component<ShowCodeAllyProps> {
|
||||
static displayName: string;
|
||||
private _container: RefObject<HTMLElement> | undefined;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
componentDidMount(): void {
|
||||
const {
|
||||
@ -133,8 +133,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
|
||||
this._container?.current?.focus();
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -237,7 +236,6 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
|
||||
challenge => challenge.id === challengeId
|
||||
);
|
||||
const titleContext = t('learn.github-link');
|
||||
|
||||
return showCodeAlly ? (
|
||||
<LearnLayout>
|
||||
<Helmet title={windowTitle} />
|
||||
@ -252,7 +250,7 @@ class ShowCodeAlly extends Component<ShowCodeAllyProps> {
|
||||
</LearnLayout>
|
||||
) : (
|
||||
<Hotkeys
|
||||
innerRef={this._container}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import { navigate } from 'gatsby';
|
||||
import React, { MutableRefObject, RefObject } from 'react';
|
||||
import React from 'react';
|
||||
import { HotKeys, GlobalHotKeys } from 'react-hotkeys';
|
||||
import { connect } from 'react-redux';
|
||||
import { createSelector } from 'reselect';
|
||||
import { editor } from 'monaco-editor';
|
||||
import type {
|
||||
ChallengeFiles,
|
||||
Test,
|
||||
@ -25,6 +24,7 @@ import {
|
||||
} from '../redux/selectors';
|
||||
import './hotkeys.css';
|
||||
import { isFinalProject } from '../../../../../shared/config/challenge-types';
|
||||
import type { EditorProps } from '../classic/editor';
|
||||
|
||||
const mapStateToProps = createSelector(
|
||||
canFocusEditorSelector,
|
||||
@ -61,24 +61,32 @@ const keyMap = {
|
||||
showShortcuts: 'shift+/'
|
||||
};
|
||||
|
||||
interface HotkeysProps
|
||||
extends Pick<ChallengeMeta, 'nextChallengePath' | 'prevChallengePath'> {
|
||||
canFocusEditor: boolean;
|
||||
challengeFiles: ChallengeFiles;
|
||||
challengeType?: number;
|
||||
children: React.ReactElement;
|
||||
editorRef?: MutableRefObject<editor.IStandaloneCodeEditor | undefined>;
|
||||
executeChallenge?: (options?: { showCompletionModal: boolean }) => void;
|
||||
submitChallenge: () => void;
|
||||
innerRef: RefObject<HTMLElement> | undefined;
|
||||
instructionsPanelRef?: React.RefObject<HTMLElement>;
|
||||
setEditorFocusability: (arg0: boolean) => void;
|
||||
setIsAdvancing: (arg0: boolean) => void;
|
||||
tests: Test[];
|
||||
usesMultifileEditor?: boolean;
|
||||
openShortcutsModal: () => void;
|
||||
user: User;
|
||||
}
|
||||
export type HotkeysProps = Pick<
|
||||
ChallengeMeta,
|
||||
'nextChallengePath' | 'prevChallengePath'
|
||||
> &
|
||||
Partial<
|
||||
Pick<
|
||||
EditorProps,
|
||||
'usesMultifileEditor' | 'editorRef' | 'challengeType' | 'executeChallenge'
|
||||
>
|
||||
> &
|
||||
Pick<
|
||||
EditorProps,
|
||||
| 'containerRef'
|
||||
| 'tests'
|
||||
| 'challengeFiles'
|
||||
| 'submitChallenge'
|
||||
| 'setEditorFocusability'
|
||||
> & {
|
||||
canFocusEditor: boolean;
|
||||
children: React.ReactElement;
|
||||
instructionsPanelRef?: React.RefObject<HTMLElement>;
|
||||
setEditorFocusability: (arg0: boolean) => void;
|
||||
setIsAdvancing: (arg0: boolean) => void;
|
||||
openShortcutsModal: () => void;
|
||||
user: User;
|
||||
};
|
||||
|
||||
function Hotkeys({
|
||||
canFocusEditor,
|
||||
@ -87,7 +95,7 @@ function Hotkeys({
|
||||
instructionsPanelRef,
|
||||
editorRef,
|
||||
executeChallenge,
|
||||
innerRef,
|
||||
containerRef,
|
||||
nextChallengePath,
|
||||
prevChallengePath,
|
||||
setEditorFocusability,
|
||||
@ -99,12 +107,12 @@ function Hotkeys({
|
||||
user: { keyboardShortcuts }
|
||||
}: HotkeysProps): JSX.Element {
|
||||
const handlers = {
|
||||
executeChallenge: (e?: KeyboardEvent) => {
|
||||
executeChallenge: (keyEvent?: KeyboardEvent) => {
|
||||
// the 'enter' part of 'ctrl+enter' stops HotKeys from listening, so it
|
||||
// needs to be prevented.
|
||||
// TODO: 'enter' on its own also disables HotKeys, but default behaviour
|
||||
// should not be prevented in that case.
|
||||
e?.preventDefault();
|
||||
keyEvent?.preventDefault();
|
||||
|
||||
if (!executeChallenge) return;
|
||||
|
||||
@ -126,8 +134,8 @@ function Hotkeys({
|
||||
},
|
||||
...(keyboardShortcuts
|
||||
? {
|
||||
focusEditor: (e?: KeyboardEvent) => {
|
||||
e?.preventDefault();
|
||||
focusEditor: (keyEvent?: KeyboardEvent) => {
|
||||
keyEvent?.preventDefault();
|
||||
if (editorRef && editorRef.current) {
|
||||
editorRef.current.focus();
|
||||
}
|
||||
@ -158,8 +166,8 @@ function Hotkeys({
|
||||
}
|
||||
}
|
||||
},
|
||||
showShortcuts: (e?: KeyboardEvent) => {
|
||||
if (!canFocusEditor && e?.shiftKey && e.key === '?') {
|
||||
showShortcuts: (keyEvent?: KeyboardEvent) => {
|
||||
if (!canFocusEditor && keyEvent?.shiftKey && keyEvent.key === '?') {
|
||||
openShortcutsModal();
|
||||
}
|
||||
}
|
||||
@ -177,7 +185,7 @@ function Hotkeys({
|
||||
id='editor-layout'
|
||||
allowChanges={true}
|
||||
handlers={handlers}
|
||||
innerRef={innerRef}
|
||||
innerRef={containerRef}
|
||||
keyMap={keyMap}
|
||||
>
|
||||
{children}
|
||||
|
||||
@ -138,7 +138,7 @@ function convertMd(md: string): string {
|
||||
|
||||
class ShowExam extends Component<ShowExamProps, ShowExamState> {
|
||||
static displayName: string;
|
||||
private _container: RefObject<HTMLElement> | undefined;
|
||||
private container: RefObject<HTMLElement> | undefined = React.createRef();
|
||||
timerInterval!: NodeJS.Timeout;
|
||||
|
||||
constructor(props: ShowExamProps) {
|
||||
@ -179,7 +179,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
|
||||
this._container?.current?.focus();
|
||||
this.container?.current?.focus();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
@ -513,7 +513,7 @@ class ShowExam extends Component<ShowExamProps, ShowExamState> {
|
||||
</Container>
|
||||
) : (
|
||||
<Hotkeys
|
||||
innerRef={this._container}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
|
||||
@ -84,7 +84,7 @@ interface MsTrophyProps {
|
||||
// Component
|
||||
class MsTrophy extends Component<MsTrophyProps> {
|
||||
static displayName: string;
|
||||
private _container: HTMLElement | null = null;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
constructor(props: MsTrophyProps) {
|
||||
super(props);
|
||||
@ -108,7 +108,7 @@ class MsTrophy extends Component<MsTrophyProps> {
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
this._container?.focus();
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: MsTrophyProps): void {
|
||||
@ -178,7 +178,7 @@ class MsTrophy extends Component<MsTrophyProps> {
|
||||
|
||||
return (
|
||||
<Hotkeys
|
||||
innerRef={(c: HTMLElement | null) => (this._container = c)}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
|
||||
@ -83,7 +83,7 @@ interface ShowOdinState {
|
||||
// Component
|
||||
class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
|
||||
static displayName: string;
|
||||
private _container: HTMLElement | null | undefined;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
constructor(props: ShowOdinProps) {
|
||||
super(props);
|
||||
@ -119,7 +119,7 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
this._container?.focus();
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ShowOdinProps): void {
|
||||
@ -236,7 +236,7 @@ class ShowOdin extends Component<ShowOdinProps, ShowOdinState> {
|
||||
executeChallenge={() => {
|
||||
this.handleSubmit(solution, openCompletionModal, assignments);
|
||||
}}
|
||||
innerRef={(c: HTMLElement | null) => (this._container = c)}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
|
||||
@ -99,8 +99,8 @@ interface BackEndProps {
|
||||
// Component
|
||||
class BackEnd extends Component<BackEndProps> {
|
||||
static displayName: string;
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
private _container: any;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
constructor(props: BackEndProps) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
@ -111,7 +111,7 @@ class BackEnd extends Component<BackEndProps> {
|
||||
componentDidMount() {
|
||||
this.initializeComponent();
|
||||
window.addEventListener('resize', () => this.updateDimensions());
|
||||
this._container.focus();
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
updateDimensions() {
|
||||
@ -220,7 +220,7 @@ class BackEnd extends Component<BackEndProps> {
|
||||
|
||||
return (
|
||||
<Hotkeys
|
||||
innerRef={(c: HTMLElement | null) => (this._container = c)}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
|
||||
@ -64,7 +64,7 @@ interface ProjectProps {
|
||||
// Component
|
||||
class Project extends Component<ProjectProps> {
|
||||
static displayName: string;
|
||||
private _container: HTMLElement | null = null;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
constructor(props: ProjectProps) {
|
||||
super(props);
|
||||
@ -88,7 +88,7 @@ class Project extends Component<ProjectProps> {
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
this._container?.focus();
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ProjectProps): void {
|
||||
@ -161,7 +161,7 @@ class Project extends Component<ProjectProps> {
|
||||
|
||||
return (
|
||||
<Hotkeys
|
||||
innerRef={(c: HTMLElement | null) => (this._container = c)}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
|
||||
@ -83,7 +83,7 @@ interface ShowVideoState {
|
||||
// Component
|
||||
class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
||||
static displayName: string;
|
||||
private _container: HTMLElement | null | undefined;
|
||||
private container: React.RefObject<HTMLElement> = React.createRef();
|
||||
|
||||
constructor(props: ShowVideoProps) {
|
||||
super(props);
|
||||
@ -117,7 +117,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
||||
helpCategory
|
||||
});
|
||||
challengeMounted(challengeMeta.id);
|
||||
this._container?.focus();
|
||||
this.container.current?.focus();
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps: ShowVideoProps): void {
|
||||
@ -213,7 +213,7 @@ class ShowVideo extends Component<ShowVideoProps, ShowVideoState> {
|
||||
executeChallenge={() => {
|
||||
this.handleSubmit(solution, openCompletionModal);
|
||||
}}
|
||||
innerRef={(c: HTMLElement | null) => (this._container = c)}
|
||||
containerRef={this.container}
|
||||
nextChallengePath={nextChallengePath}
|
||||
prevChallengePath={prevChallengePath}
|
||||
>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user