fix: make crtl+s save to appropriate location (#45406)
* fix: crtl+s for multifile cert projects * fix: remove unused code * feat: add limit on save frequency
This commit is contained in:
@ -533,6 +533,7 @@
|
|||||||
"local-code-saved": "Saved! Your code was saved to your browser's local storage.",
|
"local-code-saved": "Saved! Your code was saved to your browser's local storage.",
|
||||||
"code-saved": "Your code was saved to the database. It will be here when you return.",
|
"code-saved": "Your code was saved to the database. It will be here when you return.",
|
||||||
"code-save-error": "An error occurred trying to save your code.",
|
"code-save-error": "An error occurred trying to save your code.",
|
||||||
|
"code-save-less": "Slow Down! Your code was not saved. Try again in a few seconds.",
|
||||||
"challenge-save-too-big": "Sorry, you cannot save your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org",
|
"challenge-save-too-big": "Sorry, you cannot save your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org",
|
||||||
"challenge-submit-too-big": "Sorry, you cannot submit your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org"
|
"challenge-submit-too-big": "Sorry, you cannot submit your code. Your code is {{user-size}} bytes. We allow a maximum of {{max-size}} bytes. Please make your code smaller and try again or request assistance on https://forum.freecodecamp.org"
|
||||||
},
|
},
|
||||||
|
@ -9,6 +9,7 @@ export enum FlashMessages {
|
|||||||
ChallengeSubmitTooBig = 'flash.challenge-submit-too-big',
|
ChallengeSubmitTooBig = 'flash.challenge-submit-too-big',
|
||||||
CodeSaved = 'flash.code-saved',
|
CodeSaved = 'flash.code-saved',
|
||||||
CodeSaveError = 'flash.code-save-error',
|
CodeSaveError = 'flash.code-save-error',
|
||||||
|
CodeSaveLess = 'flash.code-save-less',
|
||||||
CompleteProjectFirst = 'flash.complete-project-first',
|
CompleteProjectFirst = 'flash.complete-project-first',
|
||||||
DeleteTokenErr = 'flash.delete-token-err',
|
DeleteTokenErr = 'flash.delete-token-err',
|
||||||
EmailValid = 'flash.email-valid',
|
EmailValid = 'flash.email-valid',
|
||||||
|
@ -13,11 +13,23 @@ import {
|
|||||||
bodySizeFits,
|
bodySizeFits,
|
||||||
MAX_BODY_SIZE
|
MAX_BODY_SIZE
|
||||||
} from '../utils/challenge-request-helpers';
|
} from '../utils/challenge-request-helpers';
|
||||||
import { saveChallengeComplete } from './';
|
import { saveChallengeComplete, savedChallengesSelector } from './';
|
||||||
|
|
||||||
export function* saveChallengeSaga() {
|
export function* saveChallengeSaga() {
|
||||||
const { id, challengeType } = yield select(challengeMetaSelector);
|
const { id, challengeType } = yield select(challengeMetaSelector);
|
||||||
const { challengeFiles } = yield select(challengeDataSelector);
|
const { challengeFiles } = yield select(challengeDataSelector);
|
||||||
|
const savedChallenges = yield select(savedChallengesSelector);
|
||||||
|
const savedChallenge = savedChallenges.find(challenge => challenge.id === id);
|
||||||
|
|
||||||
|
// don't let users save more than once every 5 seconds
|
||||||
|
if (Date.now() - savedChallenge?.lastSavedDate < 5000) {
|
||||||
|
return yield put(
|
||||||
|
createFlashMessage({
|
||||||
|
type: 'danger',
|
||||||
|
message: FlashMessages.CodeSaveLess
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// only allow saving of multiFileCertProject's
|
// only allow saving of multiFileCertProject's
|
||||||
if (challengeType === challengeTypes.multiFileCertProject) {
|
if (challengeType === challengeTypes.multiFileCertProject) {
|
||||||
|
@ -20,7 +20,11 @@ import store from 'store';
|
|||||||
|
|
||||||
import { Loader } from '../../../components/helpers';
|
import { Loader } from '../../../components/helpers';
|
||||||
import { Themes } from '../../../components/settings/theme';
|
import { Themes } from '../../../components/settings/theme';
|
||||||
import { userSelector, isDonationModalOpenSelector } from '../../../redux';
|
import {
|
||||||
|
userSelector,
|
||||||
|
saveChallenge,
|
||||||
|
isDonationModalOpenSelector
|
||||||
|
} from '../../../redux';
|
||||||
import {
|
import {
|
||||||
ChallengeFiles,
|
ChallengeFiles,
|
||||||
Dimensions,
|
Dimensions,
|
||||||
@ -31,8 +35,10 @@ import {
|
|||||||
} from '../../../redux/prop-types';
|
} from '../../../redux/prop-types';
|
||||||
import { editorToneOptions } from '../../../utils/tone/editor-config';
|
import { editorToneOptions } from '../../../utils/tone/editor-config';
|
||||||
import { editorNotes } from '../../../utils/tone/editor-notes';
|
import { editorNotes } from '../../../utils/tone/editor-notes';
|
||||||
|
import { challengeTypes } from '../../../../utils/challenge-types';
|
||||||
import {
|
import {
|
||||||
canFocusEditorSelector,
|
canFocusEditorSelector,
|
||||||
|
challengeMetaSelector,
|
||||||
consoleOutputSelector,
|
consoleOutputSelector,
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
saveEditorContent,
|
saveEditorContent,
|
||||||
@ -54,6 +60,7 @@ const MonacoEditor = Loadable(() => import('react-monaco-editor'));
|
|||||||
interface EditorProps {
|
interface EditorProps {
|
||||||
canFocus: boolean;
|
canFocus: boolean;
|
||||||
challengeFiles: ChallengeFiles;
|
challengeFiles: ChallengeFiles;
|
||||||
|
challengeType: number;
|
||||||
containerRef: RefObject<HTMLElement>;
|
containerRef: RefObject<HTMLElement>;
|
||||||
contents: string;
|
contents: string;
|
||||||
description: string;
|
description: string;
|
||||||
@ -70,6 +77,7 @@ interface EditorProps {
|
|||||||
isResetting: boolean;
|
isResetting: boolean;
|
||||||
output: string[];
|
output: string[];
|
||||||
resizeProps: ResizeProps;
|
resizeProps: ResizeProps;
|
||||||
|
saveChallenge: () => void;
|
||||||
saveEditorContent: () => void;
|
saveEditorContent: () => void;
|
||||||
setEditorFocusability: (isFocusable: boolean) => void;
|
setEditorFocusability: (isFocusable: boolean) => void;
|
||||||
submitChallenge: () => void;
|
submitChallenge: () => void;
|
||||||
@ -105,6 +113,7 @@ interface EditorProperties {
|
|||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
canFocusEditorSelector,
|
canFocusEditorSelector,
|
||||||
|
challengeMetaSelector,
|
||||||
consoleOutputSelector,
|
consoleOutputSelector,
|
||||||
isDonationModalOpenSelector,
|
isDonationModalOpenSelector,
|
||||||
isProjectPreviewModalOpenSelector,
|
isProjectPreviewModalOpenSelector,
|
||||||
@ -113,6 +122,7 @@ const mapStateToProps = createSelector(
|
|||||||
challengeTestsSelector,
|
challengeTestsSelector,
|
||||||
(
|
(
|
||||||
canFocus: boolean,
|
canFocus: boolean,
|
||||||
|
{ challengeType }: { challengeType: number },
|
||||||
output: string[],
|
output: string[],
|
||||||
open,
|
open,
|
||||||
previewOpen: boolean,
|
previewOpen: boolean,
|
||||||
@ -121,6 +131,7 @@ const mapStateToProps = createSelector(
|
|||||||
tests: [{ text: string; testString: string }]
|
tests: [{ text: string; testString: string }]
|
||||||
) => ({
|
) => ({
|
||||||
canFocus: open ? false : canFocus,
|
canFocus: open ? false : canFocus,
|
||||||
|
challengeType,
|
||||||
previewOpen,
|
previewOpen,
|
||||||
isResetting,
|
isResetting,
|
||||||
output,
|
output,
|
||||||
@ -133,6 +144,7 @@ const mapStateToProps = createSelector(
|
|||||||
|
|
||||||
const mapDispatchToProps = {
|
const mapDispatchToProps = {
|
||||||
executeChallenge,
|
executeChallenge,
|
||||||
|
saveChallenge,
|
||||||
saveEditorContent,
|
saveEditorContent,
|
||||||
setEditorFocusability,
|
setEditorFocusability,
|
||||||
updateFile,
|
updateFile,
|
||||||
@ -396,9 +408,14 @@ const Editor = (props: EditorProps): JSX.Element => {
|
|||||||
});
|
});
|
||||||
editor.addAction({
|
editor.addAction({
|
||||||
id: 'save-editor-content',
|
id: 'save-editor-content',
|
||||||
label: 'Save editor content to localStorage',
|
label: 'Save editor content',
|
||||||
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S],
|
keybindings: [monaco.KeyMod.CtrlCmd | monaco.KeyCode.KEY_S],
|
||||||
run: props.saveEditorContent
|
run:
|
||||||
|
props.challengeType === challengeTypes.multiFileCertProject
|
||||||
|
? // save to database
|
||||||
|
props.saveChallenge
|
||||||
|
: // save to local storage
|
||||||
|
props.saveEditorContent
|
||||||
});
|
});
|
||||||
editor.addAction({
|
editor.addAction({
|
||||||
id: 'toggle-accessibility',
|
id: 'toggle-accessibility',
|
||||||
|
@ -24,6 +24,7 @@ const toneUrls = {
|
|||||||
[FlashMessages.ChallengeSubmitTooBig]: TRY_AGAIN,
|
[FlashMessages.ChallengeSubmitTooBig]: TRY_AGAIN,
|
||||||
[FlashMessages.CodeSaved]: CHAL_COMP,
|
[FlashMessages.CodeSaved]: CHAL_COMP,
|
||||||
[FlashMessages.CodeSaveError]: TRY_AGAIN,
|
[FlashMessages.CodeSaveError]: TRY_AGAIN,
|
||||||
|
[FlashMessages.CodeSaveLess]: TRY_AGAIN,
|
||||||
[FlashMessages.CompleteProjectFirst]: TRY_AGAIN,
|
[FlashMessages.CompleteProjectFirst]: TRY_AGAIN,
|
||||||
[FlashMessages.DeleteTokenErr]: TRY_AGAIN,
|
[FlashMessages.DeleteTokenErr]: TRY_AGAIN,
|
||||||
[FlashMessages.EmailValid]: CHAL_COMP,
|
[FlashMessages.EmailValid]: CHAL_COMP,
|
||||||
|
Reference in New Issue
Block a user