mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
fix: block creation and hot reloading (#66127)
This commit is contained in:
committed by
GitHub
parent
ba61d33698
commit
a6d1e545c0
@@ -135,9 +135,7 @@ exports.createChallengePages = function (
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: figure out a cleaner way to get the last challenge in a block. Create
|
// TODO: figure out a cleaner way to get the last challenge in a block.
|
||||||
// it during the curriculum build process and attach it to the first challenge?
|
|
||||||
// That would remove the need to analyse allChallengeEdges.
|
|
||||||
function getProjectPreviewConfig(challenge, allChallengeNodes) {
|
function getProjectPreviewConfig(challenge, allChallengeNodes) {
|
||||||
const { block } = challenge;
|
const { block } = challenge;
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
insertStepIntoMeta,
|
insertStepIntoMeta,
|
||||||
updateStepTitles
|
updateStepTitles
|
||||||
} from './utils.js';
|
} from './utils.js';
|
||||||
|
import { ObjectId } from 'bson';
|
||||||
|
|
||||||
async function deleteStep(stepNum: number): Promise<void> {
|
async function deleteStep(stepNum: number): Promise<void> {
|
||||||
if (stepNum < 1) {
|
if (stepNum < 1) {
|
||||||
@@ -54,13 +55,16 @@ async function insertStep(stepNum: number): Promise<void> {
|
|||||||
const challengeType =
|
const challengeType =
|
||||||
previousChallenge?.challengeType ?? nextChallenge?.challengeType;
|
previousChallenge?.challengeType ?? nextChallenge?.challengeType;
|
||||||
|
|
||||||
const stepId = createStepFile({
|
const challengeId = new ObjectId();
|
||||||
|
|
||||||
|
createStepFile({
|
||||||
|
challengeId,
|
||||||
stepNum,
|
stepNum,
|
||||||
challengeType,
|
challengeType,
|
||||||
challengeSeeds
|
challengeSeeds
|
||||||
});
|
});
|
||||||
|
|
||||||
await insertStepIntoMeta({ stepNum, stepId });
|
await insertStepIntoMeta({ stepNum, stepId: challengeId });
|
||||||
updateStepTitles();
|
updateStepTitles();
|
||||||
console.log(`Successfully inserted new step #${stepNum}`);
|
console.log(`Successfully inserted new step #${stepNum}`);
|
||||||
}
|
}
|
||||||
@@ -74,8 +78,9 @@ async function createEmptySteps(num: number): Promise<void> {
|
|||||||
|
|
||||||
const nextStepNum = getMetaData().challengeOrder.length + 1;
|
const nextStepNum = getMetaData().challengeOrder.length + 1;
|
||||||
for (let stepNum = nextStepNum; stepNum < nextStepNum + num; stepNum++) {
|
for (let stepNum = nextStepNum; stepNum < nextStepNum + num; stepNum++) {
|
||||||
const stepId = createStepFile({ stepNum });
|
const challengeId = new ObjectId();
|
||||||
await insertStepIntoMeta({ stepNum, stepId });
|
createStepFile({ stepNum, challengeId });
|
||||||
|
await insertStepIntoMeta({ stepNum, stepId: challengeId });
|
||||||
}
|
}
|
||||||
console.log(`Successfully added ${num} steps`);
|
console.log(`Successfully added ${num} steps`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,23 +96,7 @@ async function createLanguageBlock(
|
|||||||
});
|
});
|
||||||
|
|
||||||
const challengeLang = getLangFromSuperBlock(superBlock);
|
const challengeLang = getLangFromSuperBlock(superBlock);
|
||||||
let challengeId: ObjectId;
|
const challengeId: ObjectId = new ObjectId();
|
||||||
|
|
||||||
if (blockLabel === BlockLabel.quiz) {
|
|
||||||
challengeId = await createQuizChallenge(
|
|
||||||
block,
|
|
||||||
title,
|
|
||||||
questionCount!,
|
|
||||||
challengeLang
|
|
||||||
);
|
|
||||||
blockLayout = BlockLayouts.Link;
|
|
||||||
} else {
|
|
||||||
challengeId = await createDialogueChallenge(
|
|
||||||
superBlock,
|
|
||||||
block,
|
|
||||||
challengeLang
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await createMetaJson(
|
await createMetaJson(
|
||||||
block,
|
block,
|
||||||
@@ -123,6 +107,19 @@ async function createLanguageBlock(
|
|||||||
blockLayout
|
blockLayout
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (blockLabel === BlockLabel.quiz) {
|
||||||
|
await createQuizChallenge(
|
||||||
|
challengeId,
|
||||||
|
block,
|
||||||
|
title,
|
||||||
|
questionCount!,
|
||||||
|
challengeLang
|
||||||
|
);
|
||||||
|
blockLayout = BlockLayouts.Link;
|
||||||
|
} else {
|
||||||
|
await createDialogueChallenge(challengeId, block, challengeLang);
|
||||||
|
}
|
||||||
|
|
||||||
const superblockFilename = (
|
const superblockFilename = (
|
||||||
superBlockToFilename as Record<SuperBlocks, string>
|
superBlockToFilename as Record<SuperBlocks, string>
|
||||||
)[superBlock];
|
)[superBlock];
|
||||||
@@ -242,7 +239,7 @@ async function createMetaJson(
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function createDialogueChallenge(
|
async function createDialogueChallenge(
|
||||||
superBlock: SuperBlocks,
|
challengeId: ObjectId,
|
||||||
block: string,
|
block: string,
|
||||||
challengeLang: string
|
challengeLang: string
|
||||||
): Promise<ObjectId> {
|
): Promise<ObjectId> {
|
||||||
@@ -254,18 +251,21 @@ async function createDialogueChallenge(
|
|||||||
await fs.mkdir(newChallengeDir, { recursive: true });
|
await fs.mkdir(newChallengeDir, { recursive: true });
|
||||||
|
|
||||||
return createDialogueFile({
|
return createDialogueFile({
|
||||||
|
challengeId,
|
||||||
projectPath: newChallengeDir + '/',
|
projectPath: newChallengeDir + '/',
|
||||||
challengeLang: challengeLang
|
challengeLang: challengeLang
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createQuizChallenge(
|
async function createQuizChallenge(
|
||||||
|
challengeId: ObjectId,
|
||||||
block: string,
|
block: string,
|
||||||
title: string,
|
title: string,
|
||||||
questionCount: number,
|
questionCount: number,
|
||||||
challengeLang: string
|
challengeLang: string
|
||||||
): Promise<ObjectId> {
|
): Promise<ObjectId> {
|
||||||
return createQuizFile({
|
return createQuizFile({
|
||||||
|
challengeId,
|
||||||
projectPath: await createBlockFolder(block),
|
projectPath: await createBlockFolder(block),
|
||||||
title: title,
|
title: title,
|
||||||
dashedName: block,
|
dashedName: block,
|
||||||
@@ -602,4 +602,4 @@ void getAllBlocks()
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.then(() => console.log('All set. Restart the client to see the changes.'));
|
.then(() => console.log('All set. Refresh the page to see the changes.'));
|
||||||
|
|||||||
@@ -97,27 +97,29 @@ async function createProject(projectArgs: CreateProjectArgs) {
|
|||||||
projectArgs.title
|
projectArgs.title
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const challengeId = new ObjectId();
|
||||||
|
|
||||||
if (projectArgs.blockLabel === BlockLabel.quiz) {
|
if (projectArgs.blockLabel === BlockLabel.quiz) {
|
||||||
if (projectArgs.questionCount == null) {
|
if (projectArgs.questionCount == null) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Property `questionCount` is null when creating new Quiz Challenge'
|
'Property `questionCount` is null when creating new Quiz Challenge'
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const challengeId = await createQuizChallenge(
|
await createMetaJson(
|
||||||
projectArgs.block,
|
|
||||||
projectArgs.title,
|
|
||||||
projectArgs.questionCount
|
|
||||||
);
|
|
||||||
void createMetaJson(
|
|
||||||
projectArgs.superBlock,
|
projectArgs.superBlock,
|
||||||
projectArgs.block,
|
projectArgs.block,
|
||||||
projectArgs.title,
|
projectArgs.title,
|
||||||
projectArgs.helpCategory,
|
projectArgs.helpCategory,
|
||||||
challengeId
|
challengeId
|
||||||
);
|
);
|
||||||
|
await createQuizChallenge({
|
||||||
|
challengeId,
|
||||||
|
block: projectArgs.block,
|
||||||
|
title: projectArgs.title,
|
||||||
|
questionCount: projectArgs.questionCount
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
const challengeId = await createFirstChallenge(projectArgs.block);
|
await createMetaJson(
|
||||||
void createMetaJson(
|
|
||||||
projectArgs.superBlock,
|
projectArgs.superBlock,
|
||||||
projectArgs.block,
|
projectArgs.block,
|
||||||
projectArgs.title,
|
projectArgs.title,
|
||||||
@@ -127,7 +129,7 @@ async function createProject(projectArgs: CreateProjectArgs) {
|
|||||||
projectArgs.blockLabel,
|
projectArgs.blockLabel,
|
||||||
projectArgs.blockLayout
|
projectArgs.blockLayout
|
||||||
);
|
);
|
||||||
// TODO: remove once we stop relying on markdown in the client.
|
await createFirstChallenge({ block: projectArgs.block, challengeId });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@@ -192,7 +194,13 @@ async function createMetaJson(
|
|||||||
await writeBlockStructure(block, newMeta);
|
await writeBlockStructure(block, newMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createFirstChallenge(block: string): Promise<ObjectId> {
|
async function createFirstChallenge({
|
||||||
|
block,
|
||||||
|
challengeId
|
||||||
|
}: {
|
||||||
|
block: string;
|
||||||
|
challengeId: ObjectId;
|
||||||
|
}) {
|
||||||
// TODO: would be nice if the extension made sense for the challenge, but, at
|
// TODO: would be nice if the extension made sense for the challenge, but, at
|
||||||
// least until react I think they're all going to be html anyway.
|
// least until react I think they're all going to be html anyway.
|
||||||
const challengeSeeds = [
|
const challengeSeeds = [
|
||||||
@@ -203,7 +211,8 @@ async function createFirstChallenge(block: string): Promise<ObjectId> {
|
|||||||
}
|
}
|
||||||
];
|
];
|
||||||
// including trailing slash for compatibility with createStepFile
|
// including trailing slash for compatibility with createStepFile
|
||||||
return createStepFile({
|
createStepFile({
|
||||||
|
challengeId,
|
||||||
projectPath: await createBlockFolder(block),
|
projectPath: await createBlockFolder(block),
|
||||||
stepNum: 1,
|
stepNum: 1,
|
||||||
challengeType: 0,
|
challengeType: 0,
|
||||||
@@ -212,12 +221,19 @@ async function createFirstChallenge(block: string): Promise<ObjectId> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createQuizChallenge(
|
async function createQuizChallenge({
|
||||||
block: string,
|
challengeId,
|
||||||
title: string,
|
block,
|
||||||
questionCount: number
|
title,
|
||||||
): Promise<ObjectId> {
|
questionCount
|
||||||
|
}: {
|
||||||
|
challengeId: ObjectId;
|
||||||
|
block: string;
|
||||||
|
title: string;
|
||||||
|
questionCount: number;
|
||||||
|
}): Promise<ObjectId> {
|
||||||
return createQuizFile({
|
return createQuizFile({
|
||||||
|
challengeId,
|
||||||
projectPath: await createBlockFolder(block),
|
projectPath: await createBlockFolder(block),
|
||||||
title: title,
|
title: title,
|
||||||
dashedName: block,
|
dashedName: block,
|
||||||
@@ -396,4 +412,4 @@ void getAllBlocks()
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.then(() => console.log('All set. Restart the client to see the changes.'));
|
.then(() => console.log('All set. Refresh the page to see the changes.'));
|
||||||
|
|||||||
@@ -40,25 +40,21 @@ interface CreateQuizArgs {
|
|||||||
questionCount: number;
|
questionCount: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createQuiz(
|
async function createQuiz({
|
||||||
superBlock: SuperBlocks,
|
superBlock,
|
||||||
block: string,
|
block,
|
||||||
helpCategory: string,
|
helpCategory,
|
||||||
questionCount: number,
|
questionCount,
|
||||||
title?: string
|
title
|
||||||
) {
|
}: CreateQuizArgs) {
|
||||||
if (!title) {
|
if (!title) {
|
||||||
title = block;
|
title = block;
|
||||||
}
|
}
|
||||||
await updateIntroJson(superBlock, block, title);
|
await updateIntroJson(superBlock, block, title);
|
||||||
|
|
||||||
const challengeId = await createQuizChallenge(
|
const challengeId = new ObjectId();
|
||||||
superBlock,
|
|
||||||
block,
|
|
||||||
title,
|
|
||||||
questionCount
|
|
||||||
);
|
|
||||||
await createMetaJson(block, title, helpCategory, challengeId);
|
await createMetaJson(block, title, helpCategory, challengeId);
|
||||||
|
await createQuizChallenge({ challengeId, block, title, questionCount });
|
||||||
const superblockFilename = (
|
const superblockFilename = (
|
||||||
superBlockToFilename as Record<SuperBlocks, string>
|
superBlockToFilename as Record<SuperBlocks, string>
|
||||||
)[superBlock];
|
)[superBlock];
|
||||||
@@ -102,13 +98,19 @@ async function createMetaJson(
|
|||||||
await writeBlockStructure(block, newMeta);
|
await writeBlockStructure(block, newMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function createQuizChallenge(
|
async function createQuizChallenge({
|
||||||
superBlock: SuperBlocks,
|
block,
|
||||||
block: string,
|
challengeId,
|
||||||
title: string,
|
title,
|
||||||
questionCount: number
|
questionCount
|
||||||
): Promise<ObjectId> {
|
}: {
|
||||||
return createQuizFile({
|
block: string;
|
||||||
|
challengeId: ObjectId;
|
||||||
|
title: string;
|
||||||
|
questionCount: number;
|
||||||
|
}) {
|
||||||
|
createQuizFile({
|
||||||
|
challengeId,
|
||||||
projectPath: await createBlockFolder(block),
|
projectPath: await createBlockFolder(block),
|
||||||
title: title,
|
title: title,
|
||||||
dashedName: block,
|
dashedName: block,
|
||||||
@@ -173,14 +175,5 @@ void getAllBlocks()
|
|||||||
}
|
}
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
.then(
|
.then(async (args: CreateQuizArgs) => await createQuiz(args))
|
||||||
async ({
|
|
||||||
superBlock,
|
|
||||||
block,
|
|
||||||
title,
|
|
||||||
helpCategory,
|
|
||||||
questionCount
|
|
||||||
}: CreateQuizArgs) =>
|
|
||||||
await createQuiz(superBlock, block, helpCategory, questionCount, title)
|
|
||||||
)
|
|
||||||
.then(() => console.log('All set. Restart the client to see the changes.'));
|
.then(() => console.log('All set. Restart the client to see the changes.'));
|
||||||
|
|||||||
@@ -108,4 +108,4 @@ void getAllBlocks()
|
|||||||
async ({ newBlock, newName, oldBlock }: RenameBlockArgs) =>
|
async ({ newBlock, newName, oldBlock }: RenameBlockArgs) =>
|
||||||
await renameBlock({ newBlock, newName, oldBlock })
|
await renameBlock({ newBlock, newName, oldBlock })
|
||||||
)
|
)
|
||||||
.then(() => console.log('All set. Restart the client to see the changes.'));
|
.then(() => console.log('All set. Refresh the page to see the changes.'));
|
||||||
|
|||||||
@@ -23,16 +23,6 @@ vi.mock('gray-matter', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
vi.mock('bson', () => {
|
|
||||||
return {
|
|
||||||
ObjectId: vi.fn().mockImplementation(function () {
|
|
||||||
return {
|
|
||||||
toString: () => mockChallengeId
|
|
||||||
};
|
|
||||||
})
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
vi.mock('./helpers/get-step-template', () => {
|
vi.mock('./helpers/get-step-template', () => {
|
||||||
return {
|
return {
|
||||||
getStepTemplate: vi.fn()
|
getStepTemplate: vi.fn()
|
||||||
@@ -50,7 +40,6 @@ vi.mock('./helpers/project-metadata', () => {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const mockChallengeId = '60d35cf3fe32df2ce8e31b03';
|
|
||||||
import { getStepTemplate } from './helpers/get-step-template.js';
|
import { getStepTemplate } from './helpers/get-step-template.js';
|
||||||
import {
|
import {
|
||||||
createChallengeFile,
|
createChallengeFile,
|
||||||
@@ -75,25 +64,26 @@ describe('Challenge utils helper scripts', () => {
|
|||||||
vi.clearAllMocks();
|
vi.clearAllMocks();
|
||||||
});
|
});
|
||||||
describe('createStepFile util', () => {
|
describe('createStepFile util', () => {
|
||||||
it('should create next step and return its identifier', () => {
|
it('should create next step', () => {
|
||||||
process.env.INIT_CWD = projectPath;
|
process.env.INIT_CWD = projectPath;
|
||||||
const mockTemplate = 'Mock template...';
|
const mockTemplate = 'Mock template...';
|
||||||
(getStepTemplate as ReturnType<typeof vi.fn>).mockReturnValue(
|
(getStepTemplate as ReturnType<typeof vi.fn>).mockReturnValue(
|
||||||
mockTemplate
|
mockTemplate
|
||||||
);
|
);
|
||||||
const step = createStepFile({
|
|
||||||
|
const challengeId = new ObjectId();
|
||||||
|
|
||||||
|
createStepFile({
|
||||||
|
challengeId,
|
||||||
stepNum: 3,
|
stepNum: 3,
|
||||||
challengeType: 0
|
challengeType: 0
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(step.toString()).toEqual(mockChallengeId);
|
|
||||||
expect(ObjectId).toHaveBeenCalledTimes(1);
|
|
||||||
|
|
||||||
// Internal tasks
|
// Internal tasks
|
||||||
// - Should generate a template for the step that is being created
|
// - Should generate a template for the step that is being created
|
||||||
expect(getStepTemplate).toHaveBeenCalledTimes(1);
|
expect(getStepTemplate).toHaveBeenCalledTimes(1);
|
||||||
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
expect(fs.writeFileSync).toHaveBeenCalledWith(
|
||||||
`${projectPath}/${mockChallengeId}.md`,
|
`${projectPath}/${challengeId.toString()}.md`,
|
||||||
mockTemplate
|
mockTemplate
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -146,15 +136,17 @@ describe('Challenge utils helper scripts', () => {
|
|||||||
it('should call updateMetaData with a new file id and name', async () => {
|
it('should call updateMetaData with a new file id and name', async () => {
|
||||||
process.env.INIT_CWD = projectPath;
|
process.env.INIT_CWD = projectPath;
|
||||||
|
|
||||||
|
const stepId = new ObjectId();
|
||||||
|
|
||||||
await insertStepIntoMeta({
|
await insertStepIntoMeta({
|
||||||
stepNum: 3,
|
stepNum: 3,
|
||||||
stepId: new ObjectId(mockChallengeId)
|
stepId
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(updateMetaData).toHaveBeenCalledWith({
|
expect(updateMetaData).toHaveBeenCalledWith({
|
||||||
challengeOrder: [
|
challengeOrder: [
|
||||||
{ id: 'abc', title: 'Step 1' }, // title gets overwritten
|
{ id: 'abc', title: 'Step 1' }, // title gets overwritten
|
||||||
{ id: mockChallengeId, title: 'Step 2' }
|
{ id: stepId.toString(), title: 'Step 2' }
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {
|
|||||||
import { getTemplate } from './helpers/get-challenge-template.js';
|
import { getTemplate } from './helpers/get-challenge-template.js';
|
||||||
|
|
||||||
interface Options {
|
interface Options {
|
||||||
|
challengeId: ObjectId;
|
||||||
stepNum: number;
|
stepNum: number;
|
||||||
challengeType?: number;
|
challengeType?: number;
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
@@ -26,6 +27,7 @@ interface Options {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface QuizOptions {
|
interface QuizOptions {
|
||||||
|
challengeId: ObjectId;
|
||||||
projectPath?: string;
|
projectPath?: string;
|
||||||
title: string;
|
title: string;
|
||||||
dashedName: string;
|
dashedName: string;
|
||||||
@@ -49,13 +51,12 @@ export async function getAllBlocks() {
|
|||||||
const createStepFile = ({
|
const createStepFile = ({
|
||||||
stepNum,
|
stepNum,
|
||||||
challengeType,
|
challengeType,
|
||||||
|
challengeId,
|
||||||
projectPath = getProjectPath(),
|
projectPath = getProjectPath(),
|
||||||
challengeSeeds = [],
|
challengeSeeds = [],
|
||||||
isFirstChallenge = false,
|
isFirstChallenge = false,
|
||||||
challengeLang
|
challengeLang
|
||||||
}: Options): ObjectId => {
|
}: Options) => {
|
||||||
const challengeId = new ObjectId();
|
|
||||||
|
|
||||||
const template = getStepTemplate({
|
const template = getStepTemplate({
|
||||||
challengeId,
|
challengeId,
|
||||||
challengeSeeds,
|
challengeSeeds,
|
||||||
@@ -66,8 +67,6 @@ const createStepFile = ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, template);
|
fs.writeFileSync(`${projectPath}${challengeId.toString()}.md`, template);
|
||||||
|
|
||||||
return challengeId;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const createChallengeFile = (
|
const createChallengeFile = (
|
||||||
@@ -79,13 +78,13 @@ const createChallengeFile = (
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createQuizFile = ({
|
const createQuizFile = ({
|
||||||
|
challengeId,
|
||||||
projectPath = getProjectPath(),
|
projectPath = getProjectPath(),
|
||||||
title,
|
title,
|
||||||
dashedName,
|
dashedName,
|
||||||
questionCount,
|
questionCount,
|
||||||
challengeLang
|
challengeLang
|
||||||
}: QuizOptions): ObjectId => {
|
}: QuizOptions): ObjectId => {
|
||||||
const challengeId = new ObjectId();
|
|
||||||
const challengeType = challengeTypes.quiz.toString();
|
const challengeType = challengeTypes.quiz.toString();
|
||||||
const template = getTemplate(challengeType);
|
const template = getTemplate(challengeType);
|
||||||
|
|
||||||
@@ -103,13 +102,14 @@ const createQuizFile = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const createDialogueFile = ({
|
const createDialogueFile = ({
|
||||||
|
challengeId,
|
||||||
projectPath,
|
projectPath,
|
||||||
challengeLang
|
challengeLang
|
||||||
}: {
|
}: {
|
||||||
|
challengeId: ObjectId;
|
||||||
projectPath: string;
|
projectPath: string;
|
||||||
challengeLang: string;
|
challengeLang: string;
|
||||||
}): ObjectId => {
|
}): ObjectId => {
|
||||||
const challengeId = new ObjectId();
|
|
||||||
const challengeType = challengeTypes.dialogue.toString();
|
const challengeType = challengeTypes.dialogue.toString();
|
||||||
const template = getTemplate(challengeType);
|
const template = getTemplate(challengeType);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
const chokidar = require('chokidar');
|
const chokidar = require('chokidar');
|
||||||
|
|
||||||
|
const { sortBy } = require('lodash');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
getSuperblockStructure
|
getSuperblockStructure
|
||||||
} = require('@freecodecamp/curriculum/file-handler');
|
} = require('@freecodecamp/curriculum/file-handler');
|
||||||
@@ -16,8 +18,6 @@ const {
|
|||||||
// createPagesStatefully only runs once, but we need the following when
|
// createPagesStatefully only runs once, but we need the following when
|
||||||
// updating challenges, so they have to be stored in memory.
|
// updating challenges, so they have to be stored in memory.
|
||||||
let allChallengeNodes;
|
let allChallengeNodes;
|
||||||
let idToNextPathCurrentCurriculum;
|
|
||||||
let idToPrevPathCurrentCurriculum;
|
|
||||||
const filepathToStatefullyCreatedNodes = new Map();
|
const filepathToStatefullyCreatedNodes = new Map();
|
||||||
const filePathToCreatedNodes = new Map();
|
const filePathToCreatedNodes = new Map();
|
||||||
// reverse lookup, to detect if an updated file has "overwritten" another file
|
// reverse lookup, to detect if an updated file has "overwritten" another file
|
||||||
@@ -120,6 +120,10 @@ exports.sourceNodes = function sourceChallengesSourceNodes(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleChallengeUpdate(filePath, action = 'changed') {
|
function handleChallengeUpdate(filePath, action = 'changed') {
|
||||||
|
// This has to be a blunt instrument, since we're not watching the structure
|
||||||
|
// files. If a .md file changes, we have to assume the structure may have
|
||||||
|
// changed too and update the structure nodes accordingly.
|
||||||
|
createSuperBlockStructureNodes();
|
||||||
if (action === 'deleted') {
|
if (action === 'deleted') {
|
||||||
// We have to return before calling onSourceChange, since the file is
|
// We have to return before calling onSourceChange, since the file is
|
||||||
// gone.
|
// gone.
|
||||||
@@ -254,6 +258,22 @@ exports.sourceNodes = function sourceChallengesSourceNodes(
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createIdToNextPathMap = nodes =>
|
||||||
|
nodes.reduce((map, node, index) => {
|
||||||
|
const nextNode = nodes[index + 1];
|
||||||
|
const nextPath = nextNode ? nextNode.challenge.fields.slug : null;
|
||||||
|
if (nextPath) map[node.id] = nextPath;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const createIdToPrevPathMap = nodes =>
|
||||||
|
nodes.reduce((map, node, index) => {
|
||||||
|
const prevNode = nodes[index - 1];
|
||||||
|
const prevPath = prevNode ? prevNode.challenge.fields.slug : null;
|
||||||
|
if (prevPath) map[node.id] = prevPath;
|
||||||
|
return map;
|
||||||
|
}, {});
|
||||||
|
|
||||||
exports.createPagesStatefully = async function ({ graphql, actions }) {
|
exports.createPagesStatefully = async function ({ graphql, actions }) {
|
||||||
const result = await graphql(`
|
const result = await graphql(`
|
||||||
{
|
{
|
||||||
@@ -322,25 +342,10 @@ exports.createPagesStatefully = async function ({ graphql, actions }) {
|
|||||||
({ node }) => node
|
({ node }) => node
|
||||||
);
|
);
|
||||||
|
|
||||||
const createIdToNextPathMap = nodes =>
|
const idToNextPathCurrentCurriculum =
|
||||||
nodes.reduce((map, node, index) => {
|
createIdToNextPathMap(allChallengeNodes);
|
||||||
const nextNode = nodes[index + 1];
|
const idToPrevPathCurrentCurriculum =
|
||||||
const nextPath = nextNode ? nextNode.challenge.fields.slug : null;
|
createIdToPrevPathMap(allChallengeNodes);
|
||||||
if (nextPath) map[node.id] = nextPath;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
const createIdToPrevPathMap = nodes =>
|
|
||||||
nodes.reduce((map, node, index) => {
|
|
||||||
const prevNode = nodes[index - 1];
|
|
||||||
const prevPath = prevNode ? prevNode.challenge.fields.slug : null;
|
|
||||||
if (prevPath) map[node.id] = prevPath;
|
|
||||||
return map;
|
|
||||||
}, {});
|
|
||||||
|
|
||||||
idToNextPathCurrentCurriculum = createIdToNextPathMap(allChallengeNodes);
|
|
||||||
|
|
||||||
idToPrevPathCurrentCurriculum = createIdToPrevPathMap(allChallengeNodes);
|
|
||||||
|
|
||||||
const nodeToPage = createChallengePages(actions.createPage, {
|
const nodeToPage = createChallengePages(actions.createPage, {
|
||||||
idToNextPathCurrentCurriculum,
|
idToNextPathCurrentCurriculum,
|
||||||
@@ -352,15 +357,27 @@ exports.createPagesStatefully = async function ({ graphql, actions }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
exports.createPages = function ({ actions }) {
|
exports.createPages = function ({ actions }) {
|
||||||
|
if (!allChallengeNodes) return;
|
||||||
|
|
||||||
// actions.createPage has to be called in the createPages hook
|
// actions.createPage has to be called in the createPages hook
|
||||||
const nodes = [...filePathToCreatedNodes.values()].flat();
|
const newNodes = [...filePathToCreatedNodes.values()].flat();
|
||||||
for (const node of nodes) {
|
// Nodes need sorting so createChallengePages can find the first and last
|
||||||
|
// challenges in a block.
|
||||||
|
const sortedNodes = sortBy(
|
||||||
|
[...allChallengeNodes, ...newNodes],
|
||||||
|
['challenge.superOrder', 'challenge.order', 'challenge.challengeOrder']
|
||||||
|
);
|
||||||
|
|
||||||
|
const idToNextPathCurrentCurriculum = createIdToNextPathMap(sortedNodes);
|
||||||
|
const idToPrevPathCurrentCurriculum = createIdToPrevPathMap(sortedNodes);
|
||||||
|
|
||||||
|
for (const node of newNodes) {
|
||||||
const nodeToPage = createChallengePages(actions.createPage, {
|
const nodeToPage = createChallengePages(actions.createPage, {
|
||||||
idToNextPathCurrentCurriculum,
|
idToNextPathCurrentCurriculum,
|
||||||
idToPrevPathCurrentCurriculum
|
idToPrevPathCurrentCurriculum
|
||||||
});
|
});
|
||||||
|
|
||||||
nodeToPage(node, 0, allChallengeNodes);
|
nodeToPage(node, 0, sortedNodes);
|
||||||
}
|
}
|
||||||
|
|
||||||
// It's important NOT to clear the createdNodes, since Gatsby deletes any
|
// It's important NOT to clear the createdNodes, since Gatsby deletes any
|
||||||
|
|||||||
Reference in New Issue
Block a user