diff --git a/.gitmodules b/.gitmodules index 513eae4a9a7..fc8e491adcf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -2,3 +2,7 @@ path = curriculum/i18n-curriculum url = https://github.com/freeCodeCamp/i18n-curriculum.git ignore = dirty +[submodule "tools/challenge-editor"] + path = tools/challenge-editor + url = https://github.com/freeCodeCamp/challenge-editor.git + ignore = dirty diff --git a/.prettierignore b/.prettierignore index 9cb0f95bbd8..808f6557f0b 100644 --- a/.prettierignore +++ b/.prettierignore @@ -20,4 +20,5 @@ shared/utils/get-lines.test.js shared/utils/is-audited.js shared/utils/validate.js shared/utils/validate.test.js +tools/challenge-editor dist diff --git a/package.json b/package.json index 46e292354f1..1ce04128a70 100644 --- a/package.json +++ b/package.json @@ -26,9 +26,8 @@ "build:client": "cd ./client && pnpm run build", "build:curriculum": "pnpm -F=curriculum run build && pnpm -F=client run build:external-curriculum", "build:api": "cd ./api && pnpm run build", - "challenge-editor": "npm-run-all -p challenge-editor:*", - "challenge-editor:client": "cd ./tools/challenge-editor/client && pnpm start", - "challenge-editor:server": "cd ./tools/challenge-editor/api && pnpm start", + "challenge-editor": "cd tools/challenge-editor && pnpm dev", + "challenge-editor-setup": "git submodule update --init tools/challenge-editor && cd tools/challenge-editor && pnpm install", "clean": "npm-run-all -p clean:client clean:api clean:curriculum --serial clean:packages", "clean-and-develop": "pnpm run clean && pnpm install && pnpm run develop", "clean:api": "cd api && pnpm clean", diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 90f48091f5b..2d929c727e7 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -4,8 +4,6 @@ packages: - 'curriculum' - 'e2e' - 'shared' - - 'tools/challenge-editor/api' - - 'tools/challenge-editor/client' - 'tools/challenge-helper-scripts' - 'tools/challenge-parser' - 'tools/client-plugins/*' diff --git a/tools/challenge-editor b/tools/challenge-editor new file mode 160000 index 00000000000..2c28b22fe04 --- /dev/null +++ b/tools/challenge-editor @@ -0,0 +1 @@ +Subproject commit 2c28b22fe040c551e45b7e40eefa19f91d59cfde diff --git a/tools/challenge-editor/api/.lintstagedrc.mjs b/tools/challenge-editor/api/.lintstagedrc.mjs deleted file mode 100644 index 2cb8879f45f..00000000000 --- a/tools/challenge-editor/api/.lintstagedrc.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable filenames-simple/naming-convention */ -import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged'; - -export default createLintStagedConfig(import.meta.dirname); diff --git a/tools/challenge-editor/api/configs/paths.ts b/tools/challenge-editor/api/configs/paths.ts deleted file mode 100644 index fc185059ffe..00000000000 --- a/tools/challenge-editor/api/configs/paths.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { join } from 'path'; - -export const SUPERBLOCK_META_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'curriculum', - 'structure', - 'superblocks' -); - -export const BLOCK_META_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'curriculum', - 'structure', - 'blocks' -); - -export const CHALLENGE_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'curriculum', - 'challenges', - 'english', - 'blocks' -); - -export const ENGLISH_LANG_DIR = join( - process.cwd(), - '..', - '..', - '..', - 'client', - 'i18n', - 'locales', - 'english' -); diff --git a/tools/challenge-editor/api/configs/super-block-list.ts b/tools/challenge-editor/api/configs/super-block-list.ts deleted file mode 100644 index 745c2f1fb6e..00000000000 --- a/tools/challenge-editor/api/configs/super-block-list.ts +++ /dev/null @@ -1,118 +0,0 @@ -export const superBlockList = [ - { - name: 'Legacy Responsive Web Design', - path: 'responsive-web-design' - }, - { - name: 'Legacy JavaScript Algorithms and Data Structures', - path: 'javascript-algorithms-and-data-structures' - }, - { - name: 'Front End Development Libraries', - path: 'front-end-development-libraries' - }, - { - name: 'Data Visualization', - path: 'data-visualization' - }, - { - name: 'Back End Development and APIs', - path: 'back-end-development-and-apis' - }, - { - name: 'Quality Assurance', - path: 'quality-assurance' - }, - { - name: 'Scientific Computing with Python', - path: 'scientific-computing-with-python' - }, - { - name: 'Data Analysis with Python', - path: 'data-analysis-with-python' - }, - { - name: 'Information Security', - path: 'information-security' - }, - { - name: 'Coding Interview Prep', - path: 'coding-interview-prep' - }, - { - name: 'Machine Learning with Python', - path: 'machine-learning-with-python' - }, - { - name: 'Relational Databases', - path: 'relational-databases' - }, - { - name: 'Responsive Web Design', - path: 'responsive-web-design-22' - }, - { - name: 'JavaScript Algorithms and Data Structures', - path: 'javascript-algorithms-and-data-structures-22' - }, - { - name: 'The Odin Project', - path: 'the-odin-project' - }, - { - name: 'College Algebra with Python', - path: 'college-algebra-with-python' - }, - { - name: 'Project Euler', - path: 'project-euler' - }, - { - name: '(New) Foundational C# with Microsoft', - path: 'foundational-c-sharp-with-microsoft' - }, - { - name: 'A2 English for Developers', - path: 'a2-english-for-developers' - }, - { - name: 'Rosetta Code', - path: 'rosetta-code' - }, - { - name: 'Python For Everybody', - path: 'python-for-everybody' - }, - { - name: 'B1 English for Developers (Beta)', - path: 'b1-english-for-developers' - }, - { - name: 'Certified Full Stack Developer', - path: 'full-stack-developer' - }, - { - name: 'A1 Professional Spanish (Beta)', - path: 'a1-professional-spanish' - }, - { - name: 'A2 Professional Spanish (Beta)', - path: 'a2-professional-spanish' - }, - { - name: 'A2 Professional Chinese (Beta)', - path: 'a2-professional-chinese' - }, - { - name: 'Basic HTML', - path: 'basic-html' - }, - { - name: 'Semantic HTML', - path: 'semantic-html' - }, - { - name: 'A1 Professional Chinese (Beta)', - path: 'a1-professional-chinese' - } -]; diff --git a/tools/challenge-editor/api/eslint.config.mjs b/tools/challenge-editor/api/eslint.config.mjs deleted file mode 100644 index 7f82ebde353..00000000000 --- a/tools/challenge-editor/api/eslint.config.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { configTypeChecked } from '@freecodecamp/eslint-config/base'; - -export default configTypeChecked; diff --git a/tools/challenge-editor/api/interfaces/challenge-data.ts b/tools/challenge-editor/api/interfaces/challenge-data.ts deleted file mode 100644 index 911a8bfea94..00000000000 --- a/tools/challenge-editor/api/interfaces/challenge-data.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ChallengeData { - name: string; - id: string; - path: string; -} diff --git a/tools/challenge-editor/api/interfaces/intro.ts b/tools/challenge-editor/api/interfaces/intro.ts deleted file mode 100644 index fca6969a8d8..00000000000 --- a/tools/challenge-editor/api/interfaces/intro.ts +++ /dev/null @@ -1,11 +0,0 @@ -interface SuperBlock { - title: string; - intro: string[]; - blocks: string[]; - modules?: string[]; - chapters?: string[]; -} - -export interface Intro { - [key: string]: SuperBlock; -} diff --git a/tools/challenge-editor/api/interfaces/partial-meta.ts b/tools/challenge-editor/api/interfaces/partial-meta.ts deleted file mode 100644 index 711e613a4a9..00000000000 --- a/tools/challenge-editor/api/interfaces/partial-meta.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface PartialMeta { - name: string; - dashedName: string; - challengeOrder: { id: string; title: string }[]; -} diff --git a/tools/challenge-editor/api/interfaces/superblock-meta.ts b/tools/challenge-editor/api/interfaces/superblock-meta.ts deleted file mode 100644 index 7a93317e6e3..00000000000 --- a/tools/challenge-editor/api/interfaces/superblock-meta.ts +++ /dev/null @@ -1,14 +0,0 @@ -export interface SuperBlockModule { - dashedName: string; - blocks?: string[]; -} - -export interface SuperBlockChapter { - dashedName: string; - modules: SuperBlockModule[]; -} - -export interface SuperBlockMeta { - blocks?: string[]; - chapters?: SuperBlockChapter[]; -} diff --git a/tools/challenge-editor/api/interfaces/tools.ts b/tools/challenge-editor/api/interfaces/tools.ts deleted file mode 100644 index ec22589f2bb..00000000000 --- a/tools/challenge-editor/api/interfaces/tools.ts +++ /dev/null @@ -1,16 +0,0 @@ -type ToolsFunction = ( - directory: string -) => Promise<{ stdout: string; stderr: string }>; - -type ToolsFunctionWithArg = ( - directory: string, - start: number -) => Promise<{ stdout: string; stderr: string }>; - -export interface ToolsSwitch { - 'create-next-step': ToolsFunction; - 'create-empty-steps': ToolsFunctionWithArg; - 'insert-step': ToolsFunctionWithArg; - 'delete-step': ToolsFunctionWithArg; - 'update-step-titles': ToolsFunction; -} diff --git a/tools/challenge-editor/api/package.json b/tools/challenge-editor/api/package.json deleted file mode 100644 index f7a41bab1d9..00000000000 --- a/tools/challenge-editor/api/package.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "name": "@freecodecamp/challenge-editor-api", - "version": "1.0.0", - "private": true, - "description": "Editor to help with new challenge structure", - "scripts": { - "start": "tsx server.ts", - "postinstall": "shx cp ./sample.env ./.env", - "lint": "eslint --max-warnings 0", - "type-check": "tsc --noEmit" - }, - "author": "freeCodeCamp", - "license": "BSD-3-Clause", - "dependencies": { - "cors": "2.8.5", - "express": "4.18.2", - "gray-matter": "4.0.3" - }, - "devDependencies": { - "@freecodecamp/eslint-config": "workspace:*", - "@total-typescript/ts-reset": "^0.6.1", - "@types/cors": "^2.8.13", - "@types/express": "4.17.21", - "dotenv": "16.4.5", - "eslint": "^9.39.1", - "shx": "0.3.4", - "typescript": "5.9.3" - } -} diff --git a/tools/challenge-editor/api/reset.d.ts b/tools/challenge-editor/api/reset.d.ts deleted file mode 100644 index 12bd3edc94a..00000000000 --- a/tools/challenge-editor/api/reset.d.ts +++ /dev/null @@ -1 +0,0 @@ -import '@total-typescript/ts-reset'; diff --git a/tools/challenge-editor/api/routes/block-route.ts b/tools/challenge-editor/api/routes/block-route.ts deleted file mode 100644 index c12955fd289..00000000000 --- a/tools/challenge-editor/api/routes/block-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getSteps } from '../utils/get-steps'; - -export const blockRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, block } = req.params; - - const steps = await getSteps(superblock, block); - - res.json(steps); -}; diff --git a/tools/challenge-editor/api/routes/index-route.ts b/tools/challenge-editor/api/routes/index-route.ts deleted file mode 100644 index 6721d6bebe2..00000000000 --- a/tools/challenge-editor/api/routes/index-route.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { Request, Response } from 'express'; -import { superBlockList } from '../configs/super-block-list'; - -export const indexRoute = (req: Request, res: Response): void => { - res.json(superBlockList); -}; diff --git a/tools/challenge-editor/api/routes/module-block-route.ts b/tools/challenge-editor/api/routes/module-block-route.ts deleted file mode 100644 index 61cb2ac9896..00000000000 --- a/tools/challenge-editor/api/routes/module-block-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getBlocks } from '../utils/get-full-stack-blocks'; - -export const moduleBlockRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, chapter, module } = req.params; - - const steps = await getBlocks(superblock, chapter, module); - - res.json(steps); -}; diff --git a/tools/challenge-editor/api/routes/module-route.ts b/tools/challenge-editor/api/routes/module-route.ts deleted file mode 100644 index bf393caaf58..00000000000 --- a/tools/challenge-editor/api/routes/module-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getModules } from '../utils/get-full-stack-blocks'; - -export const moduleRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, chapter } = req.params; - - const steps = await getModules(superblock, chapter); - - res.json(steps); -}; diff --git a/tools/challenge-editor/api/routes/module-step-route.ts b/tools/challenge-editor/api/routes/module-step-route.ts deleted file mode 100644 index e04d2b60280..00000000000 --- a/tools/challenge-editor/api/routes/module-step-route.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Request, Response } from 'express'; -import { getStepContent } from '../utils/get-step-contents'; - -export const stepRoute = async (req: Request, res: Response): Promise => { - const { superblock, block, step } = req.params; - - const stepContents = await getStepContent(superblock, block, step); - res.json(stepContents); -}; diff --git a/tools/challenge-editor/api/routes/save-route.ts b/tools/challenge-editor/api/routes/save-route.ts deleted file mode 100644 index beceeb8f357..00000000000 --- a/tools/challenge-editor/api/routes/save-route.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { Request, Response } from 'express'; -import { saveStep } from '../utils/save-step'; - -export const saveRoute = async (req: Request, res: Response): Promise => { - const { superblock, block, step } = req.params; - const content = (req.body as { content: string }).content; - - const success = await saveStep(superblock, block, step, content); - - const message = success - ? 'Your changes have been saved and are ready to commit!' - : 'There was an error when saving your changes. Please try again.'; - - res.json({ message }); -}; diff --git a/tools/challenge-editor/api/routes/step-route.ts b/tools/challenge-editor/api/routes/step-route.ts deleted file mode 100644 index e04d2b60280..00000000000 --- a/tools/challenge-editor/api/routes/step-route.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Request, Response } from 'express'; -import { getStepContent } from '../utils/get-step-contents'; - -export const stepRoute = async (req: Request, res: Response): Promise => { - const { superblock, block, step } = req.params; - - const stepContents = await getStepContent(superblock, block, step); - res.json(stepContents); -}; diff --git a/tools/challenge-editor/api/routes/super-block-route.ts b/tools/challenge-editor/api/routes/super-block-route.ts deleted file mode 100644 index ad50c4bb529..00000000000 --- a/tools/challenge-editor/api/routes/super-block-route.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { Request, Response } from 'express'; -import { getBlocks } from '../utils/get-blocks'; - -export const superblockRoute = async ( - req: Request, - res: Response -): Promise => { - const sup = req.params.superblock; - - const blocks = await getBlocks(sup); - - res.json(blocks); -}; diff --git a/tools/challenge-editor/api/routes/tools-route.ts b/tools/challenge-editor/api/routes/tools-route.ts deleted file mode 100644 index ff93fb470d8..00000000000 --- a/tools/challenge-editor/api/routes/tools-route.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { exec } from 'child_process'; -import { join } from 'path'; -import { promisify } from 'util'; - -import { Request, Response } from 'express'; -import { ToolsSwitch } from '../interfaces/tools'; - -const asyncExec = promisify(exec); - -const toolsSwitch: ToolsSwitch = { - 'create-next-step': directory => { - return asyncExec(`cd ${directory} && pnpm run create-next-step`); - }, - 'create-empty-steps': (directory, num) => { - return asyncExec(`cd ${directory} && pnpm run create-empty-steps ${num}`); - }, - 'insert-step': (directory, num) => { - return asyncExec(`cd ${directory} && pnpm run insert-step ${num}`); - }, - 'delete-step': (directory, num) => { - return asyncExec(`cd ${directory} && pnpm run delete-step ${num}`); - }, - 'update-step-titles': directory => { - return asyncExec(`cd ${directory} && pnpm run update-step-titles`); - } -}; - -export const toolsRoute = async ( - req: Request, - res: Response -): Promise => { - const { superblock, block, command } = req.params; - const { num } = req.body as Record; - const directory = join( - __dirname, - '..', - '..', - '..', - '..', - 'curriculum', - 'challenges', - 'english', - superblock, - block - ); - - if (!(command in toolsSwitch)) { - res.json({ stdout: '', stderr: 'Command not found' }); - return; - } - - const parsed = command as keyof ToolsSwitch; - - const { stdout, stderr } = await toolsSwitch[parsed](directory, num); - res.json({ stdout, stderr }); -}; diff --git a/tools/challenge-editor/api/sample.env b/tools/challenge-editor/api/sample.env deleted file mode 100644 index e1749a00c07..00000000000 --- a/tools/challenge-editor/api/sample.env +++ /dev/null @@ -1 +0,0 @@ -CHALLENGE_EDITOR_CLIENT_LOCATION=http://localhost:3300 diff --git a/tools/challenge-editor/api/server.ts b/tools/challenge-editor/api/server.ts deleted file mode 100644 index 1a201ac049d..00000000000 --- a/tools/challenge-editor/api/server.ts +++ /dev/null @@ -1,56 +0,0 @@ -import * as dotenv from 'dotenv'; -dotenv.config(); - -import cors from 'cors'; -import express from 'express'; -import { blockRoute } from './routes/block-route'; -import { indexRoute } from './routes/index-route'; -import { saveRoute } from './routes/save-route'; -import { stepRoute } from './routes/step-route'; -import { superblockRoute } from './routes/super-block-route'; -import { toolsRoute } from './routes/tools-route'; -import { moduleRoute } from './routes/module-route'; -import { moduleBlockRoute } from './routes/module-block-route'; - -const app = express(); - -app.use( - cors({ - origin: process.env.CHALLENGE_EDITOR_CLIENT_LOCATION - }) -); - -app.use(express.static('public')); -app.use(express.json()); - -app.post('/:superblock/:block/_tools/:command', (req, res, next) => { - toolsRoute(req, res).catch(next); -}); - -app.post('/:superblock/:block/:step', (req, res, next) => { - saveRoute(req, res).catch(next); -}); - -app.get(`/:superblock/chapters/:chapter`, (req, res, next) => { - moduleRoute(req, res).catch(next); -}); - -app.get(`/:superblock/chapters/:chapter/modules/:module`, (req, res, next) => { - moduleBlockRoute(req, res).catch(next); -}); - -app.get('/:superblock/:block/:step', (req, res, next) => { - stepRoute(req, res).catch(next); -}); - -app.get('/:superblock/:block', (req, res, next) => { - blockRoute(req, res).catch(next); -}); - -app.get('/:superblock', (req, res, next) => { - superblockRoute(req, res).catch(next); -}); - -app.get('/', indexRoute); - -app.listen(3200, () => console.log('App is live on 3200!')); diff --git a/tools/challenge-editor/api/tsconfig.json b/tools/challenge-editor/api/tsconfig.json deleted file mode 100644 index 94fe2cca852..00000000000 --- a/tools/challenge-editor/api/tsconfig.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "extends": "../../../tsconfig-base.json" -} diff --git a/tools/challenge-editor/api/utils/get-blocks.ts b/tools/challenge-editor/api/utils/get-blocks.ts deleted file mode 100644 index 586c6c33b0f..00000000000 --- a/tools/challenge-editor/api/utils/get-blocks.ts +++ /dev/null @@ -1,69 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; - -import { chapterBasedSuperBlocks } from '../../../../packages/shared/src/config/curriculum'; -import { - SUPERBLOCK_META_DIR, - BLOCK_META_DIR, - ENGLISH_LANG_DIR -} from '../configs/paths'; - -import { SuperBlockMeta } from '../interfaces/superblock-meta'; -import { PartialMeta } from '../interfaces/partial-meta'; - -import { Intro } from '../interfaces/intro'; - -type Block = { - name: string; - path: string; -}; - -type BlockLocation = { - blocks: Block[]; - currentSuperBlock: string; -}; - -export const getBlocks = async (sup: string): Promise => { - const superBlockDataPath = join(SUPERBLOCK_META_DIR, sup + '.json'); - const superBlockMetaFile = await readFile(superBlockDataPath, { - encoding: 'utf8' - }); - const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta; - - const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json'); - const introFile = await readFile(introDataPath, { - encoding: 'utf8' - }); - const introData = JSON.parse(introFile) as Intro; - - let blocks: { name: string; path: string }[] = []; - - if (chapterBasedSuperBlocks.includes(sup)) { - blocks = superBlockMeta.chapters!.map(chapter => { - const chapters = Object.entries(introData[sup]['chapters']!); - const chapterTrueName = chapters.filter( - x => x[0] === chapter.dashedName - )[0][1]; - return { - name: chapterTrueName, - path: 'chapters/' + chapter.dashedName - }; - }); - } else { - blocks = await Promise.all( - superBlockMeta.blocks!.map(async block => { - const blockStructurePath = join(BLOCK_META_DIR, block + '.json'); - const blockMetaFile = await readFile(blockStructurePath, { - encoding: 'utf8' - }); - const blockMeta = JSON.parse(blockMetaFile) as PartialMeta; - return { - name: blockMeta.name, - path: block - }; - }) - ); - } - - return { blocks: blocks, currentSuperBlock: introData[sup]?.title }; -}; diff --git a/tools/challenge-editor/api/utils/get-full-stack-blocks.ts b/tools/challenge-editor/api/utils/get-full-stack-blocks.ts deleted file mode 100644 index d798843f717..00000000000 --- a/tools/challenge-editor/api/utils/get-full-stack-blocks.ts +++ /dev/null @@ -1,131 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; -import { - SUPERBLOCK_META_DIR, - BLOCK_META_DIR, - ENGLISH_LANG_DIR -} from '../configs/paths'; -import { SuperBlockMeta } from '../interfaces/superblock-meta'; -import { PartialMeta } from '../interfaces/partial-meta'; -import { Intro } from '../interfaces/intro'; - -type Block = { - name: string; - path: string; -}; - -type Module = { - name: string; - path: string; -}; - -type BlockLocation = { - blocks: Block[]; - currentModule: string; - currentChapter: string; -}; - -type ModuleLocation = { - modules: Module[]; - currentChapter: string; - currentSuperBlock: string; -}; - -export const getModules = async ( - superBlock: string, - chap: string -): Promise => { - const superBlockDataPath = join(SUPERBLOCK_META_DIR, superBlock + '.json'); - - const superBlockMetaFile = await readFile(superBlockDataPath, { - encoding: 'utf8' - }); - const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta; - - const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json'); - const introFile = await readFile(introDataPath, { - encoding: 'utf8' - }); - const introData = JSON.parse(introFile) as Intro; - - const chapters = Object.entries(introData[superBlock]['chapters']!); - - const chapter = superBlockMeta.chapters!.filter( - x => x.dashedName === chap - )[0]; - - const chapterTrueName = chapters.filter(x => x[0] === chap)[0][1]; - - let modules: Module[] = []; - - modules = chapter.modules.map(module => { - const modules = Object.entries(introData[superBlock]['modules']!); - const moduleTrueName = modules.filter( - x => x[0] === module.dashedName - )[0][1]; - return { name: moduleTrueName, path: 'modules/' + module.dashedName }; - }); - - return { - modules: modules, - currentChapter: chapterTrueName, - currentSuperBlock: introData[superBlock].title - }; -}; - -export const getBlocks = async ( - superBlock: string, - chapterName: string, - moduleName: string -): Promise => { - const superBlockDataPath = join(SUPERBLOCK_META_DIR, superBlock + '.json'); - - const superBlockMetaFile = await readFile(superBlockDataPath, { - encoding: 'utf8' - }); - const superBlockMeta = JSON.parse(superBlockMetaFile) as SuperBlockMeta; - - const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json'); - const introFile = await readFile(introDataPath, { - encoding: 'utf8' - }); - const introData = JSON.parse(introFile) as Intro; - - const modules = Object.entries(introData[superBlock]['modules']!); - - const moduleTrueName = modules.filter(x => x[0] === moduleName)[0][1]; - - const chapters = Object.entries(introData[superBlock]['chapters']!); - - const chapterTrueName = chapters.filter(x => x[0] === chapterName)[0][1]; - - const foundChapter = superBlockMeta.chapters?.filter( - chapter => chapter.dashedName === chapterName - )[0]; - - const foundModule = foundChapter?.modules.filter( - module => module.dashedName === moduleName - )[0]; - - let blocks: { name: string; path: string }[] = []; - - blocks = await Promise.all( - foundModule!.blocks!.map(async block => { - const blockStructurePath = join(BLOCK_META_DIR, block + '.json'); - const blockMetaFile = await readFile(blockStructurePath, { - encoding: 'utf8' - }); - const blockMeta = JSON.parse(blockMetaFile) as PartialMeta; - return { - name: blockMeta.name, - path: block - }; - }) - ); - - return { - blocks: blocks, - currentModule: moduleTrueName, - currentChapter: chapterTrueName - }; -}; diff --git a/tools/challenge-editor/api/utils/get-step-contents.ts b/tools/challenge-editor/api/utils/get-step-contents.ts deleted file mode 100644 index dd71a2627fb..00000000000 --- a/tools/challenge-editor/api/utils/get-step-contents.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; -import matter from 'gray-matter'; -import { CHALLENGE_DIR } from '../configs/paths'; - -export const getStepContent = async ( - sup: string, - block: string, - step: string -): Promise<{ name: string; dashedName: string; fileData: string }> => { - const filePath = join(CHALLENGE_DIR, block, step); - - const fileData = await readFile(filePath, 'utf8'); - const name = matter(fileData).data.title as string; - const dashedName = matter(fileData).data.dashedName as string; - return { name, dashedName, fileData }; -}; diff --git a/tools/challenge-editor/api/utils/get-steps.ts b/tools/challenge-editor/api/utils/get-steps.ts deleted file mode 100644 index 586af180864..00000000000 --- a/tools/challenge-editor/api/utils/get-steps.ts +++ /dev/null @@ -1,81 +0,0 @@ -import { readFile } from 'fs/promises'; -import { join } from 'path'; - -import matter from 'gray-matter'; - -import { PartialMeta } from '../interfaces/partial-meta'; -import { - BLOCK_META_DIR, - CHALLENGE_DIR, - ENGLISH_LANG_DIR -} from '../configs/paths'; -import { Intro } from '../interfaces/intro'; - -const getFileOrder = (id: string, meta: PartialMeta) => { - return meta.challengeOrder.findIndex(({ id: f }) => f === id); -}; - -type Step = { - name: string; - id: string; - path: string; -}; - -type StepLocation = { - steps: Step[]; - currentBlock: string; - currentSuperBlock: string; -}; - -export const getSteps = async ( - sup: string, - block: string -): Promise => { - //const superMetaPath = join(SUPERBLOCK_META_DIR, sup + ".json"); - - //const superMetaData = JSON.parse( - // await readFile(superMetaPath, 'utf8') - //) as Partial; - - const stepDirectory = join(CHALLENGE_DIR, block); - - const blockFolderPath = join(BLOCK_META_DIR, block + '.json'); - - const introDataPath = join(ENGLISH_LANG_DIR, 'intro.json'); - const introFile = await readFile(introDataPath, { - encoding: 'utf8' - }); - - const introData = JSON.parse(introFile) as Intro; - - const blockMetaData = JSON.parse( - await readFile(blockFolderPath, { encoding: 'utf8' }) - ) as PartialMeta; - - const stepFileNames = blockMetaData.challengeOrder.map(x => x.id + '.md'); - - const stepData = await Promise.all( - stepFileNames.map(async filename => { - const stepPath = join(stepDirectory, filename); - const step = await readFile(stepPath, 'utf8'); - const frontMatter = matter(step); - - return { - name: frontMatter.data.title as string, - id: frontMatter.data.id as string, - path: filename - }; - }) - ); - - const steps = stepData.sort( - (a, b) => - getFileOrder(a.id, blockMetaData) - getFileOrder(b.id, blockMetaData) - ); - - return { - steps: steps, - currentBlock: blockMetaData.name, - currentSuperBlock: introData[sup]?.title - }; -}; diff --git a/tools/challenge-editor/api/utils/save-step.ts b/tools/challenge-editor/api/utils/save-step.ts deleted file mode 100644 index f8333a9ecbe..00000000000 --- a/tools/challenge-editor/api/utils/save-step.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { writeFile } from 'fs/promises'; -import { join } from 'path'; -import { CHALLENGE_DIR } from '../configs/paths'; - -export const saveStep = async ( - sup: string, - block: string, - step: string, - content: string -): Promise => { - try { - const filePath = join(CHALLENGE_DIR, block, step); - - await writeFile(filePath, content); - - return true; - } catch (err) { - console.log(err); - return false; - } -}; diff --git a/tools/challenge-editor/client/.gitignore b/tools/challenge-editor/client/.gitignore deleted file mode 100644 index 4d29575de80..00000000000 --- a/tools/challenge-editor/client/.gitignore +++ /dev/null @@ -1,23 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage - -# production -/build - -# misc -.DS_Store -.env.local -.env.development.local -.env.test.local -.env.production.local - -npm-debug.log* -yarn-debug.log* -yarn-error.log* diff --git a/tools/challenge-editor/client/.lintstagedrc.mjs b/tools/challenge-editor/client/.lintstagedrc.mjs deleted file mode 100644 index 2cb8879f45f..00000000000 --- a/tools/challenge-editor/client/.lintstagedrc.mjs +++ /dev/null @@ -1,4 +0,0 @@ -/* eslint-disable filenames-simple/naming-convention */ -import { createLintStagedConfig } from '@freecodecamp/eslint-config/lintstaged'; - -export default createLintStagedConfig(import.meta.dirname); diff --git a/tools/challenge-editor/client/eslint.config.mjs b/tools/challenge-editor/client/eslint.config.mjs deleted file mode 100644 index f75c186aa50..00000000000 --- a/tools/challenge-editor/client/eslint.config.mjs +++ /dev/null @@ -1,10 +0,0 @@ -import { - configTypeChecked, - configReact -} from '@freecodecamp/eslint-config/base'; - -export default [ - ...configTypeChecked, - ...configReact, - { settings: { react: { version: '17.0.2' } } } -]; diff --git a/tools/challenge-editor/client/index.html b/tools/challenge-editor/client/index.html deleted file mode 100644 index b683202f72c..00000000000 --- a/tools/challenge-editor/client/index.html +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - freeCodeCamp Challenge Editor - - - -
- - - diff --git a/tools/challenge-editor/client/interfaces/block.ts b/tools/challenge-editor/client/interfaces/block.ts deleted file mode 100644 index 563e6a33779..00000000000 --- a/tools/challenge-editor/client/interfaces/block.ts +++ /dev/null @@ -1,15 +0,0 @@ -export interface Block { - name: string; - path: string; -} - -export interface BlocksWithSuperBlock { - blocks: Block[]; - currentSuperBlock: string; -} - -export interface BlocksWithModule { - blocks: Block[]; - currentModule: string; - currentChapter: string; -} diff --git a/tools/challenge-editor/client/interfaces/challenge-content.ts b/tools/challenge-editor/client/interfaces/challenge-content.ts deleted file mode 100644 index 60474e17201..00000000000 --- a/tools/challenge-editor/client/interfaces/challenge-content.ts +++ /dev/null @@ -1,5 +0,0 @@ -export interface ChallengeContent { - name: string; - fileData: string; - dashedName: string; -} diff --git a/tools/challenge-editor/client/interfaces/challenge-data.ts b/tools/challenge-editor/client/interfaces/challenge-data.ts deleted file mode 100644 index b1248bb2c7b..00000000000 --- a/tools/challenge-editor/client/interfaces/challenge-data.ts +++ /dev/null @@ -1,11 +0,0 @@ -export interface ChallengeData { - name: string; - id: string; - path: string; -} - -export interface ChallengeDataWithBlock { - steps: ChallengeData[]; - currentBlock: string; - currentSuperBlock: string; -} diff --git a/tools/challenge-editor/client/interfaces/chapter.ts b/tools/challenge-editor/client/interfaces/chapter.ts deleted file mode 100644 index ff2473572bb..00000000000 --- a/tools/challenge-editor/client/interfaces/chapter.ts +++ /dev/null @@ -1,10 +0,0 @@ -export interface Module { - name: string; - path: string; -} - -export interface ChaptersWithLocation { - modules: Module[]; - currentSuperBlock: string; - currentChapter: string; -} diff --git a/tools/challenge-editor/client/interfaces/prop-types.ts b/tools/challenge-editor/client/interfaces/prop-types.ts deleted file mode 100644 index 2bb76daf4be..00000000000 --- a/tools/challenge-editor/client/interfaces/prop-types.ts +++ /dev/null @@ -1,9 +0,0 @@ -export interface BlockRequiredProps { - superblock?: string; - block?: string; -} - -export interface ChallengeContentRequiredProps extends BlockRequiredProps { - challenge?: string; - content: string; -} diff --git a/tools/challenge-editor/client/interfaces/super-block.ts b/tools/challenge-editor/client/interfaces/super-block.ts deleted file mode 100644 index 43f83133125..00000000000 --- a/tools/challenge-editor/client/interfaces/super-block.ts +++ /dev/null @@ -1,4 +0,0 @@ -export interface SuperBlock { - name: string; - path: string; -} diff --git a/tools/challenge-editor/client/package.json b/tools/challenge-editor/client/package.json deleted file mode 100644 index 454eb5581be..00000000000 --- a/tools/challenge-editor/client/package.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "name": "@freecodecamp/challenge-editor-client", - "version": "0.1.0", - "private": true, - "dependencies": { - "@vitejs/plugin-react": "4.2.1", - "codemirror": "5.65.16", - "react": "17.0.2", - "react-dom": "17.0.2", - "react-router-dom": "6.18.0", - "vite": "4.5.2" - }, - "scripts": { - "start": "PORT=3300 vite", - "build": "tsc && vite build", - "lint": "eslint --max-warnings 0", - "type-check": "tsc --noEmit", - "postinstall": "shx cp ./sample.env ./.env" - }, - "devDependencies": { - "@freecodecamp/eslint-config": "workspace:*", - "@types/codemirror": "5.60.15", - "@types/react": "17.0.83", - "@types/react-dom": "17.0.19", - "@uiw/react-codemirror": "3.2.10", - "eslint": "^9.39.1", - "shx": "0.3.4", - "typescript": "5.9.3" - } -} diff --git a/tools/challenge-editor/client/sample.env b/tools/challenge-editor/client/sample.env deleted file mode 100644 index f9c1536d2cd..00000000000 --- a/tools/challenge-editor/client/sample.env +++ /dev/null @@ -1,2 +0,0 @@ -CHALLENGE_EDITOR_API_LOCATION=http://localhost:3200 -CHALLENGE_EDITOR_LEARN_CLIENT_LOCATION=http://localhost:8000 diff --git a/tools/challenge-editor/client/src/app.tsx b/tools/challenge-editor/client/src/app.tsx deleted file mode 100644 index 7d014b84680..00000000000 --- a/tools/challenge-editor/client/src/app.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import * as React from 'react'; -import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; -import Header from './components/header/header'; -import Landing from './components/landing/landing'; -import SuperBlock from './components/superblock/super-block'; -import Block from './components/block/block'; -import Editor from './components/editor/editor'; -import Tools from './components/tools/tools'; -import ChapterLanding from './components/chapter/chapter'; -import ModuleLanding from './components/module/module'; - -const App = () => { - return ( -
-
- - - } /> - } /> - } /> - } - /> - } - /> - } /> - } /> - - -
- ); -}; - -export default App; diff --git a/tools/challenge-editor/client/src/components/block/block.css b/tools/challenge-editor/client/src/components/block/block.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/block/block.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/block/block.tsx b/tools/challenge-editor/client/src/components/block/block.tsx deleted file mode 100644 index fa94d2a7cac..00000000000 --- a/tools/challenge-editor/client/src/components/block/block.tsx +++ /dev/null @@ -1,173 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { - ChallengeData, - ChallengeDataWithBlock -} from '../../../interfaces/challenge-data'; -import { API_LOCATION } from '../../utils/handle-request'; -import './block.css'; - -const stepBasedSuperblocks = [ - 'scientific-computing-with-python', - 'responsive-web-design-22', - 'javascript-algorithms-and-data-structures-22', - 'front-end-development' -]; - -const taskBasedSuperblocks = [ - 'a2-english-for-developers', - 'b1-english-for-developers', - 'a1-professional-spanish', - 'a2-professional-spanish', - 'a2-professional-chinese', - 'a1-professional-chinese' -]; - -const Block = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as ChallengeData[]); - const [blockName, setBlockName] = useState(''); - const [superBlockName, setSuperBlockName] = useState(''); - const params = useParams() as { superblock: string; block: string }; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch(`${API_LOCATION}/${params.superblock}/${params.block}`) - .then(res => res.json() as Promise) - .then( - superblocks => { - setLoading(false); - setItems(superblocks.steps); - setBlockName(superblocks.currentBlock); - setSuperBlockName(superblocks.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - - const isStepBasedSuperblock = stepBasedSuperblocks.includes( - params.superblock - ); - - const isTaskBasedSuperblock = taskBasedSuperblocks.includes( - params.superblock - ); - - return ( -
-

{blockName}

- {superBlockName} -
    - {items.map((challenge, i) => ( -
  • - {!isStepBasedSuperblock && {`${i + 1}: `}} - - {challenge.name} - -
  • - ))} -
-

- Return to Blocks -

-
-

Project Controls

- {isStepBasedSuperblock ? ( -

- Looking to add, remove, or edit steps?{' '} - - Use the step tools. - -

- ) : isTaskBasedSuperblock ? ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-task: Create the next task style - challenge in this block -
  • -
  • - pnpm create-next-challenge: Create the next challenge - of a different style in this block -
  • -
  • - pnpm insert-task: Create a new task style challenge - in the middle of this block. -
  • -
  • - pnpm delete-task: Delete a task style challenge in - this block. -
  • -
  • - pnpm reorder-tasks: Rename the tasks to the correct - order. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - ) : ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-challenge: Create a new challenge at - the end of this block. -
  • -
  • - pnpm insert-challenge: Create a new challenge in the - middle of this block. -
  • -
  • - pnpm delete-challenge: Delete a challenge in this - block. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - )} -
- ); -}; - -export default Block; diff --git a/tools/challenge-editor/client/src/components/buttons/create-empty-steps.tsx b/tools/challenge-editor/client/src/components/buttons/create-empty-steps.tsx deleted file mode 100644 index 4fef6ad54c8..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/create-empty-steps.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import React, { useState } from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const CreateEmptySteps = ({ superblock, block }: BlockRequiredProps) => { - const [num, setNum] = useState(0); - - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${ - block || '' - }/_tools/create-empty-steps`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ num }) - } - ) - ); - - const changeNum = (e: React.ChangeEvent) => { - setNum(parseInt(e.target.value, 10)); - }; - - return ( -
- - -
- ); -}; - -export default CreateEmptySteps; diff --git a/tools/challenge-editor/client/src/components/buttons/create-next-step.tsx b/tools/challenge-editor/client/src/components/buttons/create-next-step.tsx deleted file mode 100644 index e191b18541a..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/create-next-step.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const CreateNextStep = ({ superblock, block }: BlockRequiredProps) => { - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${ - block || '' - }/_tools/create-next-step`, - { - method: 'POST' - } - ) - ); - - return ; -}; - -export default CreateNextStep; diff --git a/tools/challenge-editor/client/src/components/buttons/delete-step.tsx b/tools/challenge-editor/client/src/components/buttons/delete-step.tsx deleted file mode 100644 index 0735f04bf75..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/delete-step.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useState } from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const DeleteStep = ({ superblock, block }: BlockRequiredProps) => { - const [num, setNum] = useState(0); - - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${block || ''}/_tools/delete-step`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ num }) - } - ) - ); - - const changeNum = (e: React.ChangeEvent) => { - setNum(parseInt(e.target.value, 10)); - }; - - return ( -
- - -
- ); -}; - -export default DeleteStep; diff --git a/tools/challenge-editor/client/src/components/buttons/insert-step.tsx b/tools/challenge-editor/client/src/components/buttons/insert-step.tsx deleted file mode 100644 index 288ab091aa2..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/insert-step.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, { useState } from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const InsertStep = ({ superblock, block }: BlockRequiredProps) => { - const [num, setNum] = useState(0); - - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${block || ''}/_tools/insert-step`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ num }) - } - ) - ); - - const changeNum = (e: React.ChangeEvent) => { - setNum(parseInt(e.target.value, 10)); - }; - - return ( -
- - -
- ); -}; - -export default InsertStep; diff --git a/tools/challenge-editor/client/src/components/buttons/save-challenge.tsx b/tools/challenge-editor/client/src/components/buttons/save-challenge.tsx deleted file mode 100644 index 4dc00e0652d..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/save-challenge.tsx +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import { ChallengeContentRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const SaveChallenge = ({ - superblock, - block, - challenge, - content -}: ChallengeContentRequiredProps) => { - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${block || ''}/${challenge || ''}`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify({ content }) - } - ) - ); - - return ; -}; - -export default SaveChallenge; diff --git a/tools/challenge-editor/client/src/components/buttons/update-step-titles.tsx b/tools/challenge-editor/client/src/components/buttons/update-step-titles.tsx deleted file mode 100644 index a1707ab2191..00000000000 --- a/tools/challenge-editor/client/src/components/buttons/update-step-titles.tsx +++ /dev/null @@ -1,20 +0,0 @@ -import React from 'react'; -import { BlockRequiredProps } from '../../../interfaces/prop-types'; -import { API_LOCATION, handleRequest } from '../../utils/handle-request'; - -const UpdateStepTitles = ({ superblock, block }: BlockRequiredProps) => { - const click = handleRequest(() => - fetch( - `${API_LOCATION}/${superblock || ''}/${ - block || '' - }/_tools/update-step-titles`, - { - method: 'POST' - } - ) - ); - - return ; -}; - -export default UpdateStepTitles; diff --git a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.css b/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.tsx b/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.tsx deleted file mode 100644 index 4ef076f2c22..00000000000 --- a/tools/challenge-editor/client/src/components/chapter-based-block/chapter-block.tsx +++ /dev/null @@ -1,177 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { - ChallengeData, - ChallengeDataWithBlock -} from '../../../interfaces/challenge-data'; -import { API_LOCATION } from '../../utils/handle-request'; -import './chapter-block.css'; - -const stepBasedSuperblocks = [ - 'scientific-computing-with-python', - 'responsive-web-design-22', - 'javascript-algorithms-and-data-structures-22', - 'front-end-development' -]; - -const taskBasedSuperblocks = [ - 'a2-english-for-developers', - 'b1-english-for-developers', - 'a2-professional-spanish', - 'a2-professional-chinese', - 'a1-professional-chinese' -]; - -const ChapterBasedBlock = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [blockName, setBlockName] = useState(''); - const [superBlockName, setSuperBlockName] = useState(''); - const [items, setItems] = useState([] as ChallengeData[]); - const params = useParams() as { - superblock: string; - chapter: string; - module: string; - block: string; - }; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch(`${API_LOCATION}/${params.superblock}/${params.block}`) - .then(res => res.json() as Promise) - .then( - superblocks => { - setLoading(false); - setItems(superblocks.steps); - setBlockName(superblocks.currentBlock); - setSuperBlockName(superblocks.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - - const isStepBasedSuperblock = stepBasedSuperblocks.includes( - params.superblock - ); - - const isTaskBasedSuperblock = taskBasedSuperblocks.includes( - params.superblock - ); - - return ( -
-

{blockName}

- {superBlockName} -
    - {items.map((challenge, i) => ( -
  • - {!isStepBasedSuperblock && {`${i + 1}: `}} - - {challenge.name} - -
  • - ))} -
-

- Return to Blocks -

-
-

Project Controls

- {isStepBasedSuperblock ? ( -

- Looking to add, remove, or edit steps?{' '} - - Use the step tools. - -

- ) : isTaskBasedSuperblock ? ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-task: Create the next task style - challenge in this block -
  • -
  • - pnpm create-next-challenge: Create the next challenge - of a different style in this block -
  • -
  • - pnpm insert-task: Create a new task style challenge - in the middle of this block. -
  • -
  • - pnpm delete-task: Delete a task style challenge in - this block. -
  • -
  • - pnpm reorder-tasks: Rename the tasks to the correct - order. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - ) : ( - <> -

- Looking to add or remove challenges? Navigate to
- - curriculum/challenges/english/blocks - {`/${params.block}`} - -
- in your terminal and run the following commands: -

-
    -
  • - pnpm create-next-challenge: Create a new challenge at - the end of this block. -
  • -
  • - pnpm insert-challenge: Create a new challenge in the - middle of this block. -
  • -
  • - pnpm delete-challenge: Delete a challenge in this - block. -
  • -
-

- Refresh the page after running a command to see the changes - reflected. -

- - )} -
- ); -}; - -export default ChapterBasedBlock; diff --git a/tools/challenge-editor/client/src/components/chapter/chapter.css b/tools/challenge-editor/client/src/components/chapter/chapter.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/chapter/chapter.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/chapter/chapter.tsx b/tools/challenge-editor/client/src/components/chapter/chapter.tsx deleted file mode 100644 index b982394cb86..00000000000 --- a/tools/challenge-editor/client/src/components/chapter/chapter.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { API_LOCATION } from '../../utils/handle-request'; -import { Module, ChaptersWithLocation } from '../../../interfaces/chapter'; - -const ChapterLanding = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as Module[]); - const [chapterName, setChapterName] = useState(''); - const [superBlockName, setSuperBlockName] = useState(''); - const params = useParams() as { superblock: string; chapter: string }; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch(`${API_LOCATION}/${params.superblock}/chapters/${params.chapter}`) - .then(res => res.json() as Promise) - .then( - blockData => { - setLoading(false); - setItems(blockData.modules); - setChapterName(blockData.currentChapter); - setSuperBlockName(blockData.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{chapterName}

-
    - {items.map(chapter => ( -
  • - - {chapter.name} - -
  • - ))} -
-

- Return to {superBlockName} -

-
-

Create New Project

-

- Want to create a new project? Open your terminal and run{' '} - pnpm run create-new-project -

-
- ); -}; - -export default ChapterLanding; diff --git a/tools/challenge-editor/client/src/components/editor/editor.css b/tools/challenge-editor/client/src/components/editor/editor.css deleted file mode 100644 index 77515ad18b9..00000000000 --- a/tools/challenge-editor/client/src/components/editor/editor.css +++ /dev/null @@ -1,15 +0,0 @@ -textarea { - display: block; - margin: auto; - width: 500px; - max-width: 100vw; - height: 500px; -} - -.CodeMirror { - /* need to add important to overwrite the specificity of the classes in the dependency */ - height: 70vh !important; - max-width: 80vw; - margin: auto; - text-align: left; -} diff --git a/tools/challenge-editor/client/src/components/editor/editor.tsx b/tools/challenge-editor/client/src/components/editor/editor.tsx deleted file mode 100644 index 87c26fcd7f4..00000000000 --- a/tools/challenge-editor/client/src/components/editor/editor.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import CodeMirror from '@uiw/react-codemirror'; -import * as codemirror from 'codemirror'; -import 'codemirror/lib/codemirror.css'; -import 'codemirror/theme/material.css'; -import 'codemirror/mode/markdown/markdown'; -// we need to import this mode to get the fenced codeblock highlighting -import 'codemirror/mode/css/css'; -import 'codemirror/mode/javascript/javascript'; -import 'codemirror/mode/jsx/jsx'; -import { Link, useParams } from 'react-router-dom'; -import { ChallengeContent } from '../../../interfaces/challenge-content'; -import SaveChallenge from '../buttons/save-challenge'; -import './editor.css'; -import { API_LOCATION } from '../../utils/handle-request'; - -// only includes superblocks whose folder names don't match their dashed names? -export const superBlockNameMap: { [key: string]: string } = { - 'responsive-web-design-22': '2022/responsive-web-design', - 'javascript-algorithms-and-data-structures-22': - 'javascript-algorithms-and-data-structures-v8', - 'front-end-development': 'full-stack-developer' -}; - -const Editor = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState({ - name: '', - dashedName: '', - fileData: '' - }); - const [stepContent, setStepContent] = useState(''); - const { superblock = '', block = '', challenge = '' } = useParams(); - - const superblockUrl = - superblock in superBlockNameMap - ? superBlockNameMap[superblock] - : superblock; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch(`${API_LOCATION}/${superblock}/${block}/${challenge}`) - .then(res => res.json() as Promise) - .then( - content => { - setLoading(false); - setItems(content); - setStepContent(content.fileData); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - const handleChange = (instance: codemirror.Editor) => { - const editedContent = instance.getValue(); - setStepContent(editedContent); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{items.name}

- - {superblock} / {block} - - - -

- Return to Block -

-

- - View Live Version of the Challenge in your running development - environment - -

-
- ); -}; - -export default Editor; diff --git a/tools/challenge-editor/client/src/components/header/header.css b/tools/challenge-editor/client/src/components/header/header.css deleted file mode 100644 index 1f5201753c3..00000000000 --- a/tools/challenge-editor/client/src/components/header/header.css +++ /dev/null @@ -1,16 +0,0 @@ -.header { - width: 100vw; - margin-top: 0; - text-align: center; - background: var(--background); - color: var(--content); -} - -.header a { - color: var(--content); -} - -.header p { - margin: 0; - font-size: 2rem; -} diff --git a/tools/challenge-editor/client/src/components/header/header.tsx b/tools/challenge-editor/client/src/components/header/header.tsx deleted file mode 100644 index 2574a31310f..00000000000 --- a/tools/challenge-editor/client/src/components/header/header.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import React from 'react'; -import './header.css'; - -const Header = () => { - return ( - - ); -}; - -export default Header; diff --git a/tools/challenge-editor/client/src/components/landing/landing.tsx b/tools/challenge-editor/client/src/components/landing/landing.tsx deleted file mode 100644 index 1d301159c8d..00000000000 --- a/tools/challenge-editor/client/src/components/landing/landing.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link } from 'react-router-dom'; -import { SuperBlock } from '../../../interfaces/super-block'; -import { API_LOCATION } from '../../utils/handle-request'; - -const Landing = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as SuperBlock[]); - - useEffect(() => { - fetchData(); - }, []); - - const fetchData = () => { - setLoading(true); - fetch(API_LOCATION) - .then(res => res.json() as Promise) - .then( - superblocks => { - setLoading(false); - setItems(superblocks); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

Superblocks

-
    - {items.map(superblock => ( -
  • - {superblock.name} -
  • - ))} -
-
- ); -}; - -export default Landing; diff --git a/tools/challenge-editor/client/src/components/module/module.css b/tools/challenge-editor/client/src/components/module/module.css deleted file mode 100644 index c2229b848a5..00000000000 --- a/tools/challenge-editor/client/src/components/module/module.css +++ /dev/null @@ -1,3 +0,0 @@ -.step-grid { - column-count: 3; -} diff --git a/tools/challenge-editor/client/src/components/module/module.tsx b/tools/challenge-editor/client/src/components/module/module.tsx deleted file mode 100644 index 27cb6f58f16..00000000000 --- a/tools/challenge-editor/client/src/components/module/module.tsx +++ /dev/null @@ -1,74 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { API_LOCATION } from '../../utils/handle-request'; -import { Block, BlocksWithModule } from '../../../interfaces/block'; - -const ModuleLanding = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as Block[]); - const [moduleName, setModuleName] = useState(''); - const [chapterName, setChapterName] = useState(''); - const params = useParams() as { - superblock: string; - chapter: string; - module: string; - }; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch( - `${API_LOCATION}/${params.superblock}/chapters/${params.chapter}/modules/${params.module}` - ) - .then(res => res.json() as Promise) - .then( - moduleData => { - setLoading(false); - setItems(moduleData.blocks); - setModuleName(moduleData.currentModule); - setChapterName(moduleData.currentChapter); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{moduleName}

-
    - {items.map(block => ( -
  • - {block.name} -
  • - ))} -
-

- - Return to {chapterName} - -

-
-

Create New Project

-

- Want to create a new project? Open your terminal and run{' '} - pnpm run create-new-project -

-
- ); -}; - -export default ModuleLanding; diff --git a/tools/challenge-editor/client/src/components/superblock/super-block.tsx b/tools/challenge-editor/client/src/components/superblock/super-block.tsx deleted file mode 100644 index ee7869e83b1..00000000000 --- a/tools/challenge-editor/client/src/components/superblock/super-block.tsx +++ /dev/null @@ -1,64 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Link, useParams } from 'react-router-dom'; -import { Block, BlocksWithSuperBlock } from '../../../interfaces/block'; -import { API_LOCATION } from '../../utils/handle-request'; - -const SuperBlock = () => { - const [error, setError] = useState(null); - const [loading, setLoading] = useState(false); - const [items, setItems] = useState([] as Block[]); - const [superBlockName, setSuperBlockName] = useState(''); - const params = useParams() as { superblock: string; block: string }; - - useEffect(() => { - fetchData(); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const fetchData = () => { - setLoading(true); - fetch(`${API_LOCATION}/${params.superblock}`) - .then(res => res.json() as Promise) - .then( - blockData => { - setLoading(false); - setItems(blockData.blocks); - setSuperBlockName(blockData.currentSuperBlock); - }, - (error: Error) => { - setLoading(false); - setError(error); - } - ); - }; - - if (error) { - return
Error: {error.message}
; - } - if (loading) { - return
Loading...
; - } - return ( -
-

{superBlockName}

-
    - {items.map(block => ( -
  • - {block.name} -
  • - ))} -
-

- Return to Superblocks -

-
-

Create New Project

-

- Want to create a new project? Open your terminal and run{' '} - pnpm run create-new-project -

-
- ); -}; - -export default SuperBlock; diff --git a/tools/challenge-editor/client/src/components/tools/tools.css b/tools/challenge-editor/client/src/components/tools/tools.css deleted file mode 100644 index 7f845671bb1..00000000000 --- a/tools/challenge-editor/client/src/components/tools/tools.css +++ /dev/null @@ -1,13 +0,0 @@ -label { - display: block; - font-size: 1.2rem; - margin-bottom: 0.5rem; -} - -input { - background-color: var(--grey); - border: 2px solid var(--content); - padding: 6px 12px; - margin-left: 10px; - color: white; -} diff --git a/tools/challenge-editor/client/src/components/tools/tools.tsx b/tools/challenge-editor/client/src/components/tools/tools.tsx deleted file mode 100644 index 448356c50a4..00000000000 --- a/tools/challenge-editor/client/src/components/tools/tools.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import React, { Link, useParams } from 'react-router-dom'; -import CreateEmptySteps from '../buttons/create-empty-steps'; -import CreateNextStep from '../buttons/create-next-step'; -import DeleteStep from '../buttons/delete-step'; -import InsertStep from '../buttons/insert-step'; -import UpdateStepTitles from '../buttons/update-step-titles'; - -import './tools.css'; - -const Tools = () => { - const { block, superblock } = useParams() as { - block: string; - superblock: string; - }; - return ( -
-

Editing Steps for {block}

-

These tools will allow you to create, delete, and reorder steps.

-

Create Next Step

-

This tool creates a new step at the end of the project.

- -

Create Empty Steps

-

- This tool creates n number of empty steps at the end of the - project. -

- -

Insert Step

-

- This tool inserts a new step as the nth step. -

- -

Delete Step

-

- This tool deletes step n. -

- -

Update Step Titles

-

- This reorders the existing steps, updating the meta for the block. You - should not need to use this one unless you've manually changed the - file order. -

- -
- Return to Block -
- ); -}; - -export default Tools; diff --git a/tools/challenge-editor/client/src/fonts/Lato-Regular.woff b/tools/challenge-editor/client/src/fonts/Lato-Regular.woff deleted file mode 100644 index 9fb190c5164..00000000000 Binary files a/tools/challenge-editor/client/src/fonts/Lato-Regular.woff and /dev/null differ diff --git a/tools/challenge-editor/client/src/index.css b/tools/challenge-editor/client/src/index.css deleted file mode 100644 index bc6d251a541..00000000000 --- a/tools/challenge-editor/client/src/index.css +++ /dev/null @@ -1,62 +0,0 @@ -:root { - --nav-background: #0a0a23; - --background: #1b1b32; - --content: #f5f6f7; - --grey: #3b3b4f; - --font-family-sans-serif: 'Lato', sans-serif; - --font-family-monospace: 'Hack-ZeroSlash', monospace; -} - -@font-face { - font-family: 'Lato'; - src: url('./fonts/Lato-Regular.woff'); -} - -body { - margin: 0; - padding: 0; - font-family: var(--font-family-sans-serif); - text-align: center; - background: var(--background); - color: var(--content); -} - -ul { - list-style-type: none; - padding: 0; -} - -p, -li { - font-size: 1.2rem; -} - -a { - color: var(--content); -} - -button { - text-align: center; - vertical-align: middle; - border: 3px solid var(--content); - font-size: 16pt; - padding: 6px 12px; - margin: 10px auto; - background: var(--grey); - color: var(--content); - cursor: pointer; -} - -button:active { - color: var(--background); - background: var(--content); -} - -code { - background: var(--grey); -} - -.breadcrumb { - font-size: 1.2rem; - font-style: italic; -} diff --git a/tools/challenge-editor/client/src/index.tsx b/tools/challenge-editor/client/src/index.tsx deleted file mode 100644 index d6f53d10cda..00000000000 --- a/tools/challenge-editor/client/src/index.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import React from 'react'; -import ReactDOM from 'react-dom'; -import './index.css'; -import App from './app'; - -ReactDOM.render( - - - , - document.getElementById('root') -); diff --git a/tools/challenge-editor/client/src/utils/handle-request.ts b/tools/challenge-editor/client/src/utils/handle-request.ts deleted file mode 100644 index 12671c8c0be..00000000000 --- a/tools/challenge-editor/client/src/utils/handle-request.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const handleRequest = (makeRequest: () => Promise) => () => { - makeRequest() - .then( - res => - res.json() as Promise<{ - stdout?: string; - stderr?: string; - message?: string; - }> - ) - .then(data => { - if (data.message) { - alert(data.message); - } else { - alert(JSON.stringify(data)); - } - }) - .catch(err => console.error(err)); -}; - -export const API_LOCATION = import.meta.env - .CHALLENGE_EDITOR_API_LOCATION as string; diff --git a/tools/challenge-editor/client/src/vite-app-env.d.ts b/tools/challenge-editor/client/src/vite-app-env.d.ts deleted file mode 100644 index 35412df1a73..00000000000 --- a/tools/challenge-editor/client/src/vite-app-env.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/// - -interface ImportMetaEnv { - readonly CHALLENGE_EDITOR_LEARN_CLIENT_LOCATION: string; -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} diff --git a/tools/challenge-editor/client/tsconfig.json b/tools/challenge-editor/client/tsconfig.json deleted file mode 100644 index 7ab42d87f17..00000000000 --- a/tools/challenge-editor/client/tsconfig.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "compilerOptions": { - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - "types": ["node"] - } -} diff --git a/tools/challenge-editor/client/vite.config.ts b/tools/challenge-editor/client/vite.config.ts deleted file mode 100644 index a28154f931d..00000000000 --- a/tools/challenge-editor/client/vite.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { defineConfig } from 'vite'; -import react from '@vitejs/plugin-react'; - -// https://vitejs.dev/config/ -export default defineConfig({ - base: '/', - plugins: [react()], - envPrefix: 'CHALLENGE_EDITOR_', - server: { - port: 3300 - } -});