* fix: stop showing completion modal on steps * feat: submit steps with ctrl+enter * fix: handle ctrl+enter when not focussing editor * fix: reset tests when user types * refactor: pass showCompletionModal as an option Otherwise we have to write executeChallenge(true) which does not mean what you might reasonably expect. * fix: always executeChallenge when not on step * fix: update frontend project show * fix: handle missing payload * refactor: isProjectStep -> hasEditableRegion * refactor: more renaming * fix: make meta.json control multifile editor use * fix: update the challengeSchema correctly * Update client/src/templates/Challenges/classic/editor.tsx Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> * fix: remove logging Co-authored-by: Shaun Hamilton <shauhami020@gmail.com> Co-authored-by: Shaun Hamilton <shauhami020@gmail.com>
		
			
				
	
	
		
			122 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			122 lines
		
	
	
		
			3.7 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
const Joi = require('joi');
 | 
						|
Joi.objectId = require('joi-objectid')(Joi);
 | 
						|
 | 
						|
const { challengeTypes } = require('../../client/utils/challenge-types');
 | 
						|
 | 
						|
const slugRE = new RegExp('^[a-z0-9-]+$');
 | 
						|
 | 
						|
const fileJoi = Joi.object().keys({
 | 
						|
  fileKey: Joi.string(),
 | 
						|
  ext: Joi.string(),
 | 
						|
  name: Joi.string(),
 | 
						|
  editableRegionBoundaries: [Joi.array().items(Joi.number())],
 | 
						|
  path: Joi.string(),
 | 
						|
  error: Joi.valid(null),
 | 
						|
  head: Joi.string().allow(''),
 | 
						|
  tail: Joi.string().allow(''),
 | 
						|
  seed: Joi.string().allow(''),
 | 
						|
  contents: Joi.string().allow(''),
 | 
						|
  id: Joi.string().allow(''),
 | 
						|
  history: [Joi.array().items(Joi.string().allow('')), Joi.string().allow('')]
 | 
						|
});
 | 
						|
 | 
						|
const schema = Joi.object()
 | 
						|
  .keys({
 | 
						|
    block: Joi.string().regex(slugRE),
 | 
						|
    blockId: Joi.objectId(),
 | 
						|
    challengeOrder: Joi.number(),
 | 
						|
    removeComments: Joi.bool(),
 | 
						|
    challengeType: Joi.number().min(0).max(12).required(),
 | 
						|
    checksum: Joi.number(),
 | 
						|
    // __commentCounts is only used to test the comment replacement
 | 
						|
    __commentCounts: Joi.object(),
 | 
						|
    // TODO: require this only for normal challenges, not certs
 | 
						|
    dashedName: Joi.string().regex(slugRE),
 | 
						|
    description: Joi.when('challengeType', {
 | 
						|
      is: [challengeTypes.step, challengeTypes.video, challengeTypes.codeally],
 | 
						|
      then: Joi.string().allow(''),
 | 
						|
      otherwise: Joi.string().required()
 | 
						|
    }),
 | 
						|
    challengeFiles: Joi.array().items(fileJoi),
 | 
						|
    guideUrl: Joi.string().uri({ scheme: 'https' }),
 | 
						|
    helpCategory: Joi.valid(
 | 
						|
      'JavaScript',
 | 
						|
      'HTML-CSS',
 | 
						|
      'Python',
 | 
						|
      'Relational Databases'
 | 
						|
    ),
 | 
						|
    videoUrl: Joi.string().allow(''),
 | 
						|
    forumTopicId: Joi.number(),
 | 
						|
    id: Joi.objectId().required(),
 | 
						|
    instructions: Joi.string().allow(''),
 | 
						|
    isComingSoon: Joi.bool(),
 | 
						|
    isLocked: Joi.bool(),
 | 
						|
    isPrivate: Joi.bool(),
 | 
						|
    order: Joi.number(),
 | 
						|
    // video challenges only:
 | 
						|
    videoId: Joi.when('challengeType', {
 | 
						|
      is: challengeTypes.video,
 | 
						|
      then: Joi.string().required()
 | 
						|
    }),
 | 
						|
    videoLocaleIds: Joi.when('challengeType', {
 | 
						|
      is: challengeTypes.video,
 | 
						|
      then: Joi.object().keys({
 | 
						|
        espanol: Joi.string(),
 | 
						|
        italian: Joi.string(),
 | 
						|
        portuguese: Joi.string()
 | 
						|
      })
 | 
						|
    }),
 | 
						|
    bilibiliIds: Joi.when('challengeType', {
 | 
						|
      is: challengeTypes.video,
 | 
						|
      then: Joi.object().keys({
 | 
						|
        aid: Joi.number().required(),
 | 
						|
        bvid: Joi.string().required(),
 | 
						|
        cid: Joi.number().required()
 | 
						|
      })
 | 
						|
    }),
 | 
						|
    question: Joi.object().keys({
 | 
						|
      text: Joi.string().required(),
 | 
						|
      answers: Joi.array().items(Joi.string()).required(),
 | 
						|
      solution: Joi.number().required()
 | 
						|
    }),
 | 
						|
    required: Joi.array().items(
 | 
						|
      Joi.object().keys({
 | 
						|
        link: Joi.string(),
 | 
						|
        raw: Joi.bool(),
 | 
						|
        src: Joi.string(),
 | 
						|
        crossDomain: Joi.bool()
 | 
						|
      })
 | 
						|
    ),
 | 
						|
    solutions: Joi.array().items(Joi.array().items(fileJoi)),
 | 
						|
    superBlock: Joi.string().regex(slugRE),
 | 
						|
    superOrder: Joi.number(),
 | 
						|
    suborder: Joi.number(),
 | 
						|
    tests: Joi.array().items(
 | 
						|
      // public challenges
 | 
						|
      Joi.object().keys({
 | 
						|
        id: Joi.string().allow(''),
 | 
						|
        text: Joi.string().required(),
 | 
						|
        testString: Joi.string().allow('').required()
 | 
						|
      }),
 | 
						|
      // our tests used in certification verification
 | 
						|
      Joi.object().keys({
 | 
						|
        id: Joi.string().required(),
 | 
						|
        title: Joi.string().required()
 | 
						|
      })
 | 
						|
    ),
 | 
						|
    template: Joi.string().allow(''),
 | 
						|
    time: Joi.string().allow(''),
 | 
						|
    title: Joi.string().required(),
 | 
						|
    translationPending: Joi.bool().required(),
 | 
						|
    url: Joi.when('challengeType', {
 | 
						|
      is: challengeTypes.codeally,
 | 
						|
      then: Joi.string().required()
 | 
						|
    }),
 | 
						|
    usesMultifileEditor: Joi.boolean()
 | 
						|
  })
 | 
						|
  .xor('helpCategory', 'isPrivate');
 | 
						|
 | 
						|
exports.challengeSchemaValidator = () => {
 | 
						|
  return challenge => schema.validate(challenge);
 | 
						|
};
 |