course-format-ts 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.idea/edu-format-ts.iml +10 -0
- package/.idea/inspectionProfiles/Project_Default.xml +28 -0
- package/.idea/misc.xml +4 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/AGENTS.md +1 -0
- package/dist/courseFormat/AnswerPlaceholder.d.ts +37 -0
- package/dist/courseFormat/AnswerPlaceholder.js +101 -0
- package/dist/courseFormat/AnswerPlaceholderComparator.d.ts +4 -0
- package/dist/courseFormat/AnswerPlaceholderComparator.js +8 -0
- package/dist/courseFormat/AnswerPlaceholderDependency.d.ts +19 -0
- package/dist/courseFormat/AnswerPlaceholderDependency.js +91 -0
- package/dist/courseFormat/CheckFeedback.d.ts +20 -0
- package/dist/courseFormat/CheckFeedback.js +58 -0
- package/dist/courseFormat/CheckResult.d.ts +33 -0
- package/dist/courseFormat/CheckResult.js +58 -0
- package/dist/courseFormat/CheckResultSeverity.d.ts +7 -0
- package/dist/courseFormat/CheckResultSeverity.js +17 -0
- package/dist/courseFormat/CheckStatus.d.ts +5 -0
- package/dist/courseFormat/CheckStatus.js +9 -0
- package/dist/courseFormat/Course.d.ts +68 -0
- package/dist/courseFormat/Course.js +165 -0
- package/dist/courseFormat/CourseMode.d.ts +4 -0
- package/dist/courseFormat/CourseMode.js +8 -0
- package/dist/courseFormat/CourseVisibility.d.ts +4 -0
- package/dist/courseFormat/CourseVisibility.js +8 -0
- package/dist/courseFormat/CourseraCourse.d.ts +5 -0
- package/dist/courseFormat/CourseraCourse.js +15 -0
- package/dist/courseFormat/DescriptionFormat.d.ts +5 -0
- package/dist/courseFormat/DescriptionFormat.js +9 -0
- package/dist/courseFormat/EduCourse.d.ts +17 -0
- package/dist/courseFormat/EduCourse.js +42 -0
- package/dist/courseFormat/EduFile.d.ts +22 -0
- package/dist/courseFormat/EduFile.js +110 -0
- package/dist/courseFormat/EduFileErrorHighlightLevel.d.ts +5 -0
- package/dist/courseFormat/EduFileErrorHighlightLevel.js +9 -0
- package/dist/courseFormat/EduFormatNames.d.ts +75 -0
- package/dist/courseFormat/EduFormatNames.js +80 -0
- package/dist/courseFormat/EduTestInfo.d.ts +28 -0
- package/dist/courseFormat/EduTestInfo.js +68 -0
- package/dist/courseFormat/EduVersions.d.ts +2 -0
- package/dist/courseFormat/EduVersions.js +5 -0
- package/dist/courseFormat/FileContents.d.ts +36 -0
- package/dist/courseFormat/FileContents.js +67 -0
- package/dist/courseFormat/FileContentsFactory.d.ts +17 -0
- package/dist/courseFormat/FileContentsFactory.js +2 -0
- package/dist/courseFormat/FrameworkLesson.d.ts +9 -0
- package/dist/courseFormat/FrameworkLesson.js +28 -0
- package/dist/courseFormat/ItemContainer.d.ts +13 -0
- package/dist/courseFormat/ItemContainer.js +45 -0
- package/dist/courseFormat/JBAccountUserInfo.d.ts +9 -0
- package/dist/courseFormat/JBAccountUserInfo.js +20 -0
- package/dist/courseFormat/Language.d.ts +4 -0
- package/dist/courseFormat/Language.js +33 -0
- package/dist/courseFormat/Lesson.d.ts +20 -0
- package/dist/courseFormat/Lesson.js +56 -0
- package/dist/courseFormat/LessonContainer.d.ts +16 -0
- package/dist/courseFormat/LessonContainer.js +54 -0
- package/dist/courseFormat/PluginInfo.d.ts +7 -0
- package/dist/courseFormat/PluginInfo.js +15 -0
- package/dist/courseFormat/Section.d.ts +8 -0
- package/dist/courseFormat/Section.js +27 -0
- package/dist/courseFormat/StudyItem.d.ts +20 -0
- package/dist/courseFormat/StudyItem.js +47 -0
- package/dist/courseFormat/Tags.d.ts +16 -0
- package/dist/courseFormat/Tags.js +42 -0
- package/dist/courseFormat/TaskFile.d.ts +26 -0
- package/dist/courseFormat/TaskFile.js +72 -0
- package/dist/courseFormat/UserInfo.d.ts +3 -0
- package/dist/courseFormat/UserInfo.js +2 -0
- package/dist/courseFormat/Vendor.d.ts +7 -0
- package/dist/courseFormat/Vendor.js +14 -0
- package/dist/courseFormat/attempts/Attempt.d.ts +12 -0
- package/dist/courseFormat/attempts/Attempt.js +25 -0
- package/dist/courseFormat/attempts/AttemptBase.d.ts +9 -0
- package/dist/courseFormat/attempts/AttemptBase.js +28 -0
- package/dist/courseFormat/attempts/DataTaskAttempt.d.ts +6 -0
- package/dist/courseFormat/attempts/DataTaskAttempt.js +24 -0
- package/dist/courseFormat/attempts/Dataset.d.ts +12 -0
- package/dist/courseFormat/attempts/Dataset.js +21 -0
- package/dist/courseFormat/fileUtils.d.ts +5 -0
- package/dist/courseFormat/fileUtils.js +32 -0
- package/dist/courseFormat/hyperskill/HyperskillCourse.d.ts +13 -0
- package/dist/courseFormat/hyperskill/HyperskillCourse.js +25 -0
- package/dist/courseFormat/hyperskill/HyperskillProject.d.ts +10 -0
- package/dist/courseFormat/hyperskill/HyperskillProject.js +16 -0
- package/dist/courseFormat/hyperskill/HyperskillStage.d.ts +8 -0
- package/dist/courseFormat/hyperskill/HyperskillStage.js +20 -0
- package/dist/courseFormat/hyperskill/HyperskillTaskType.d.ts +4 -0
- package/dist/courseFormat/hyperskill/HyperskillTaskType.js +26 -0
- package/dist/courseFormat/hyperskill/HyperskillTopic.d.ts +5 -0
- package/dist/courseFormat/hyperskill/HyperskillTopic.js +11 -0
- package/dist/courseFormat/loggerUtils.d.ts +1 -0
- package/dist/courseFormat/loggerUtils.js +6 -0
- package/dist/courseFormat/stepik/StepikCourse.d.ts +5 -0
- package/dist/courseFormat/stepik/StepikCourse.js +15 -0
- package/dist/courseFormat/stepik/StepikLesson.d.ts +6 -0
- package/dist/courseFormat/stepik/StepikLesson.js +16 -0
- package/dist/courseFormat/tasks/AnswerTask.d.ts +8 -0
- package/dist/courseFormat/tasks/AnswerTask.js +11 -0
- package/dist/courseFormat/tasks/CodeTask.d.ts +12 -0
- package/dist/courseFormat/tasks/CodeTask.js +21 -0
- package/dist/courseFormat/tasks/DataTask.d.ts +18 -0
- package/dist/courseFormat/tasks/DataTask.js +32 -0
- package/dist/courseFormat/tasks/EduTask.d.ts +12 -0
- package/dist/courseFormat/tasks/EduTask.js +22 -0
- package/dist/courseFormat/tasks/IdeTask.d.ts +9 -0
- package/dist/courseFormat/tasks/IdeTask.js +14 -0
- package/dist/courseFormat/tasks/NumberTask.d.ts +9 -0
- package/dist/courseFormat/tasks/NumberTask.js +14 -0
- package/dist/courseFormat/tasks/OutputTask.d.ts +10 -0
- package/dist/courseFormat/tasks/OutputTask.js +18 -0
- package/dist/courseFormat/tasks/OutputTaskBase.d.ts +14 -0
- package/dist/courseFormat/tasks/OutputTaskBase.js +19 -0
- package/dist/courseFormat/tasks/RemoteEduTask.d.ts +9 -0
- package/dist/courseFormat/tasks/RemoteEduTask.js +15 -0
- package/dist/courseFormat/tasks/StringTask.d.ts +9 -0
- package/dist/courseFormat/tasks/StringTask.js +14 -0
- package/dist/courseFormat/tasks/TableTask.d.ts +17 -0
- package/dist/courseFormat/tasks/TableTask.js +43 -0
- package/dist/courseFormat/tasks/Task.d.ts +45 -0
- package/dist/courseFormat/tasks/Task.js +155 -0
- package/dist/courseFormat/tasks/TheoryTask.d.ts +10 -0
- package/dist/courseFormat/tasks/TheoryTask.js +15 -0
- package/dist/courseFormat/tasks/UnsupportedTask.d.ts +9 -0
- package/dist/courseFormat/tasks/UnsupportedTask.js +14 -0
- package/dist/courseFormat/tasks/choice/ChoiceOption.d.ts +10 -0
- package/dist/courseFormat/tasks/choice/ChoiceOption.js +33 -0
- package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.d.ts +5 -0
- package/dist/courseFormat/tasks/choice/ChoiceOptionStatus.js +9 -0
- package/dist/courseFormat/tasks/choice/ChoiceTask.d.ts +23 -0
- package/dist/courseFormat/tasks/choice/ChoiceTask.js +47 -0
- package/dist/courseFormat/tasks/matching/MatchingTask.d.ts +10 -0
- package/dist/courseFormat/tasks/matching/MatchingTask.js +15 -0
- package/dist/courseFormat/tasks/matching/SortingBasedTask.d.ts +16 -0
- package/dist/courseFormat/tasks/matching/SortingBasedTask.js +50 -0
- package/dist/courseFormat/tasks/matching/SortingTask.d.ts +9 -0
- package/dist/courseFormat/tasks/matching/SortingTask.js +14 -0
- package/dist/courseFormat/uiMessages.d.ts +3 -0
- package/dist/courseFormat/uiMessages.js +14 -0
- package/dist/disk-loader.d.ts +4 -0
- package/dist/disk-loader.js +389 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.js +64 -0
- package/dist/loader.d.ts +7 -0
- package/dist/loader.js +435 -0
- package/dist/models.d.ts +49 -0
- package/dist/models.js +2 -0
- package/dist/zip-loader.d.ts +4 -0
- package/dist/zip-loader.js +431 -0
- package/example-course-project/course-info.yaml +15 -0
- package/example-course-project/lesson1/lesson-info.yaml +3 -0
- package/example-course-project/lesson1/lesson-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task1/Task.txt +1 -0
- package/example-course-project/lesson1/task1/task-info.yaml +12 -0
- package/example-course-project/lesson1/task1/task-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task1/task.md +47 -0
- package/example-course-project/lesson1/task1/tests/Tests.txt +0 -0
- package/example-course-project/lesson1/task2/Task.txt +1 -0
- package/example-course-project/lesson1/task2/task-info.yaml +12 -0
- package/example-course-project/lesson1/task2/task-remote-info.yaml +1 -0
- package/example-course-project/lesson1/task2/task.md +47 -0
- package/example-course-project/lesson1/task2/tests/Tests.txt +0 -0
- package/package.json +19 -0
- package/src/@types/mime-types.d.ts +3 -0
- package/src/courseFormat/AnswerPlaceholder.ts +121 -0
- package/src/courseFormat/AnswerPlaceholderComparator.ts +7 -0
- package/src/courseFormat/AnswerPlaceholderDependency.ts +122 -0
- package/src/courseFormat/CheckFeedback.ts +71 -0
- package/src/courseFormat/CheckResult.ts +92 -0
- package/src/courseFormat/CheckResultSeverity.ts +13 -0
- package/src/courseFormat/CheckStatus.ts +5 -0
- package/src/courseFormat/Course.ts +201 -0
- package/src/courseFormat/CourseMode.ts +4 -0
- package/src/courseFormat/CourseVisibility.ts +4 -0
- package/src/courseFormat/CourseraCourse.ts +10 -0
- package/src/courseFormat/DescriptionFormat.ts +5 -0
- package/src/courseFormat/EduCourse.ts +41 -0
- package/src/courseFormat/EduFile.ts +133 -0
- package/src/courseFormat/EduFileErrorHighlightLevel.ts +5 -0
- package/src/courseFormat/EduFormatNames.ts +95 -0
- package/src/courseFormat/EduTestInfo.ts +87 -0
- package/src/courseFormat/EduVersions.ts +2 -0
- package/src/courseFormat/FileContents.ts +97 -0
- package/src/courseFormat/FileContentsFactory.ts +19 -0
- package/src/courseFormat/FrameworkLesson.ts +29 -0
- package/src/courseFormat/ItemContainer.ts +47 -0
- package/src/courseFormat/JBAccountUserInfo.ts +21 -0
- package/src/courseFormat/Language.ts +31 -0
- package/src/courseFormat/Lesson.ts +69 -0
- package/src/courseFormat/LessonContainer.ts +65 -0
- package/src/courseFormat/PluginInfo.ts +15 -0
- package/src/courseFormat/Section.ts +29 -0
- package/src/courseFormat/StudyItem.ts +55 -0
- package/src/courseFormat/Tags.ts +45 -0
- package/src/courseFormat/TaskFile.ts +88 -0
- package/src/courseFormat/UserInfo.ts +3 -0
- package/src/courseFormat/Vendor.ts +15 -0
- package/src/courseFormat/attempts/Attempt.ts +28 -0
- package/src/courseFormat/attempts/AttemptBase.ts +24 -0
- package/src/courseFormat/attempts/DataTaskAttempt.ts +19 -0
- package/src/courseFormat/attempts/Dataset.ts +13 -0
- package/src/courseFormat/fileUtils.ts +31 -0
- package/src/courseFormat/hyperskill/HyperskillCourse.ts +24 -0
- package/src/courseFormat/hyperskill/HyperskillProject.ts +10 -0
- package/src/courseFormat/hyperskill/HyperskillStage.ts +15 -0
- package/src/courseFormat/hyperskill/HyperskillTaskType.ts +23 -0
- package/src/courseFormat/hyperskill/HyperskillTopic.ts +5 -0
- package/src/courseFormat/loggerUtils.ts +3 -0
- package/src/courseFormat/stepik/StepikCourse.ts +10 -0
- package/src/courseFormat/stepik/StepikLesson.ts +11 -0
- package/src/courseFormat/tasks/AnswerTask.ts +13 -0
- package/src/courseFormat/tasks/CodeTask.ts +42 -0
- package/src/courseFormat/tasks/DataTask.ts +37 -0
- package/src/courseFormat/tasks/EduTask.ts +26 -0
- package/src/courseFormat/tasks/IdeTask.ts +17 -0
- package/src/courseFormat/tasks/NumberTask.ts +17 -0
- package/src/courseFormat/tasks/OutputTask.ts +21 -0
- package/src/courseFormat/tasks/OutputTaskBase.ts +23 -0
- package/src/courseFormat/tasks/RemoteEduTask.ts +18 -0
- package/src/courseFormat/tasks/StringTask.ts +17 -0
- package/src/courseFormat/tasks/TableTask.ts +51 -0
- package/src/courseFormat/tasks/Task.ts +181 -0
- package/src/courseFormat/tasks/TheoryTask.ts +19 -0
- package/src/courseFormat/tasks/UnsupportedTask.ts +17 -0
- package/src/courseFormat/tasks/choice/ChoiceOption.ts +37 -0
- package/src/courseFormat/tasks/choice/ChoiceOptionStatus.ts +5 -0
- package/src/courseFormat/tasks/choice/ChoiceTask.ts +57 -0
- package/src/courseFormat/tasks/matching/MatchingTask.ts +19 -0
- package/src/courseFormat/tasks/matching/SortingBasedTask.ts +59 -0
- package/src/courseFormat/tasks/matching/SortingTask.ts +17 -0
- package/src/courseFormat/uiMessages.ts +12 -0
- package/src/disk-loader.ts +463 -0
- package/src/index.ts +33 -0
- package/src/models.ts +54 -0
- package/src/zip-loader.ts +583 -0
- package/test/load-course.test.js +279 -0
- package/test/load-zip-course.test.js +73 -0
- package/tsconfig.json +15 -0
|
@@ -0,0 +1,431 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.loadCourseProjectFromZip = loadCourseProjectFromZip;
|
|
13
|
+
const fflate_1 = require("fflate");
|
|
14
|
+
const Vendor_1 = require("./courseFormat/Vendor");
|
|
15
|
+
const EduCourse_1 = require("./courseFormat/EduCourse");
|
|
16
|
+
const CourseraCourse_1 = require("./courseFormat/CourseraCourse");
|
|
17
|
+
const HyperskillCourse_1 = require("./courseFormat/hyperskill/HyperskillCourse");
|
|
18
|
+
const StepikCourse_1 = require("./courseFormat/stepik/StepikCourse");
|
|
19
|
+
const Section_1 = require("./courseFormat/Section");
|
|
20
|
+
const Lesson_1 = require("./courseFormat/Lesson");
|
|
21
|
+
const EduTask_1 = require("./courseFormat/tasks/EduTask");
|
|
22
|
+
const CodeTask_1 = require("./courseFormat/tasks/CodeTask");
|
|
23
|
+
const NumberTask_1 = require("./courseFormat/tasks/NumberTask");
|
|
24
|
+
const StringTask_1 = require("./courseFormat/tasks/StringTask");
|
|
25
|
+
const OutputTask_1 = require("./courseFormat/tasks/OutputTask");
|
|
26
|
+
const DataTask_1 = require("./courseFormat/tasks/DataTask");
|
|
27
|
+
const TableTask_1 = require("./courseFormat/tasks/TableTask");
|
|
28
|
+
const TheoryTask_1 = require("./courseFormat/tasks/TheoryTask");
|
|
29
|
+
const IdeTask_1 = require("./courseFormat/tasks/IdeTask");
|
|
30
|
+
const UnsupportedTask_1 = require("./courseFormat/tasks/UnsupportedTask");
|
|
31
|
+
const RemoteEduTask_1 = require("./courseFormat/tasks/RemoteEduTask");
|
|
32
|
+
const ChoiceTask_1 = require("./courseFormat/tasks/choice/ChoiceTask");
|
|
33
|
+
const ChoiceOption_1 = require("./courseFormat/tasks/choice/ChoiceOption");
|
|
34
|
+
const ChoiceOptionStatus_1 = require("./courseFormat/tasks/choice/ChoiceOptionStatus");
|
|
35
|
+
const MatchingTask_1 = require("./courseFormat/tasks/matching/MatchingTask");
|
|
36
|
+
const SortingTask_1 = require("./courseFormat/tasks/matching/SortingTask");
|
|
37
|
+
const TaskFile_1 = require("./courseFormat/TaskFile");
|
|
38
|
+
const AnswerPlaceholder_1 = require("./courseFormat/AnswerPlaceholder");
|
|
39
|
+
const CheckStatus_1 = require("./courseFormat/CheckStatus");
|
|
40
|
+
const CourseMode_1 = require("./courseFormat/CourseMode");
|
|
41
|
+
const EduFile_1 = require("./courseFormat/EduFile");
|
|
42
|
+
const TEST_AES_KEY = "DFC929E375655998A34E56A21C98651C";
|
|
43
|
+
// ── Main exported function ──────────────────────────
|
|
44
|
+
function loadCourseProjectFromZip(data, options) {
|
|
45
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
46
|
+
var _a;
|
|
47
|
+
const zip = (0, fflate_1.unzipSync)(data);
|
|
48
|
+
const aesKey = (_a = options === null || options === void 0 ? void 0 : options.aesKey) !== null && _a !== void 0 ? _a : TEST_AES_KEY;
|
|
49
|
+
const courseJsonRaw = zip["course.json"];
|
|
50
|
+
if (!courseJsonRaw) {
|
|
51
|
+
throw new Error("Missing course.json in zip archive");
|
|
52
|
+
}
|
|
53
|
+
const courseJson = JSON.parse(new TextDecoder().decode(courseJsonRaw));
|
|
54
|
+
if (!courseJson.title) {
|
|
55
|
+
throw new Error("Missing 'title' in course.json");
|
|
56
|
+
}
|
|
57
|
+
if (!courseJson.language) {
|
|
58
|
+
throw new Error("Missing 'language' in course.json");
|
|
59
|
+
}
|
|
60
|
+
if (!courseJson.programming_language_id) {
|
|
61
|
+
throw new Error("Missing 'programming_language_id' in course.json");
|
|
62
|
+
}
|
|
63
|
+
const course = createZipCourse(courseJson.course_type);
|
|
64
|
+
hydrateZipCourse(course, courseJson);
|
|
65
|
+
if (courseJson.items) {
|
|
66
|
+
for (const item of courseJson.items) {
|
|
67
|
+
if (item.type === "section" && item.items) {
|
|
68
|
+
yield loadZipSection(course, item, zip, aesKey);
|
|
69
|
+
}
|
|
70
|
+
else if (item.task_list) {
|
|
71
|
+
// Direct lesson at top level (uncommon for this zip but handle it)
|
|
72
|
+
yield loadZipLesson(course, item, "", zip, aesKey);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (courseJson.additional_files) {
|
|
77
|
+
loadZipAdditionalFiles(course, courseJson.additional_files, zip);
|
|
78
|
+
}
|
|
79
|
+
return course;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
// ── Course creation and hydration ────────────────────
|
|
83
|
+
function createZipCourse(type) {
|
|
84
|
+
switch (type) {
|
|
85
|
+
case "hyperskill":
|
|
86
|
+
return new HyperskillCourse_1.HyperskillCourse();
|
|
87
|
+
case "coursera":
|
|
88
|
+
return new CourseraCourse_1.CourseraCourse();
|
|
89
|
+
case "stepik":
|
|
90
|
+
return new StepikCourse_1.StepikCourse();
|
|
91
|
+
default:
|
|
92
|
+
return new EduCourse_1.EduCourse();
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
function hydrateZipCourse(course, json) {
|
|
96
|
+
var _a, _b, _c, _d, _e;
|
|
97
|
+
course.name = (_a = json.title) !== null && _a !== void 0 ? _a : "";
|
|
98
|
+
course.description = (_b = json.summary) !== null && _b !== void 0 ? _b : "";
|
|
99
|
+
course.programmingLanguage = (_c = json.programming_language_id) !== null && _c !== void 0 ? _c : "";
|
|
100
|
+
course.environment = (_d = json.environment) !== null && _d !== void 0 ? _d : course.environment;
|
|
101
|
+
course.languageCode = (_e = json.language) !== null && _e !== void 0 ? _e : "en";
|
|
102
|
+
if (json.vendor) {
|
|
103
|
+
course.vendor = new Vendor_1.Vendor(json.vendor.name, json.vendor.email, json.vendor.url);
|
|
104
|
+
}
|
|
105
|
+
if (json.course_type === "Marketplace") {
|
|
106
|
+
course.courseMode = CourseMode_1.CourseMode.STUDENT;
|
|
107
|
+
}
|
|
108
|
+
else {
|
|
109
|
+
course.courseMode = CourseMode_1.CourseMode.EDUCATOR;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
// ── Section loading ──────────────────────────────────
|
|
113
|
+
function loadZipSection(course, sectionJson, zip, aesKey) {
|
|
114
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
115
|
+
var _a, _b, _c;
|
|
116
|
+
const section = new Section_1.Section();
|
|
117
|
+
section.name = (_a = sectionJson.title) !== null && _a !== void 0 ? _a : "";
|
|
118
|
+
if (sectionJson.custom_name) {
|
|
119
|
+
section.customPresentableName = sectionJson.custom_name;
|
|
120
|
+
}
|
|
121
|
+
const sectionTitle = (_b = sectionJson.title) !== null && _b !== void 0 ? _b : "";
|
|
122
|
+
const nestedItems = (_c = sectionJson.items) !== null && _c !== void 0 ? _c : [];
|
|
123
|
+
for (const item of nestedItems) {
|
|
124
|
+
if (item.type === "lesson" && item.task_list) {
|
|
125
|
+
yield loadZipLesson(section, item, sectionTitle, zip, aesKey);
|
|
126
|
+
}
|
|
127
|
+
else if (item.task_list) {
|
|
128
|
+
// Also treat items with task_list as lessons even without explicit type
|
|
129
|
+
yield loadZipLesson(section, item, sectionTitle, zip, aesKey);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
course.addSection(section);
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
// ── Lesson loading ───────────────────────────────────
|
|
136
|
+
function loadZipLesson(parent, lessonJson, sectionTitle, zip, aesKey) {
|
|
137
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
138
|
+
var _a, _b;
|
|
139
|
+
const lesson = new Lesson_1.Lesson();
|
|
140
|
+
lesson.name = (_a = lessonJson.title) !== null && _a !== void 0 ? _a : "";
|
|
141
|
+
if (lessonJson.custom_name) {
|
|
142
|
+
lesson.customPresentableName = lessonJson.custom_name;
|
|
143
|
+
}
|
|
144
|
+
const taskList = (_b = lessonJson.task_list) !== null && _b !== void 0 ? _b : [];
|
|
145
|
+
for (const taskJson of taskList) {
|
|
146
|
+
const task = yield loadZipTask(taskJson, lessonJson, sectionTitle, zip, aesKey);
|
|
147
|
+
if (task) {
|
|
148
|
+
lesson.addTask(task);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
parent.addItem(lesson);
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// ── Task loading ─────────────────────────────────────
|
|
155
|
+
function loadZipTask(taskJson, lessonJson, sectionTitle, zip, aesKey) {
|
|
156
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
157
|
+
var _a, _b, _c, _d, _e, _f;
|
|
158
|
+
if (!taskJson.task_type) {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
const task = createZipTask(taskJson.task_type);
|
|
162
|
+
if (!task) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
// Basic fields
|
|
166
|
+
task.name = (_a = taskJson.name) !== null && _a !== void 0 ? _a : "";
|
|
167
|
+
task.id = (_b = taskJson.id) !== null && _b !== void 0 ? _b : 0;
|
|
168
|
+
if (taskJson.custom_name) {
|
|
169
|
+
task.customPresentableName = taskJson.custom_name;
|
|
170
|
+
}
|
|
171
|
+
task.descriptionText = (_c = taskJson.description_text) !== null && _c !== void 0 ? _c : "";
|
|
172
|
+
if (taskJson.description_format) {
|
|
173
|
+
task.descriptionFormat = taskJson.description_format;
|
|
174
|
+
}
|
|
175
|
+
if (taskJson.feedback_link !== undefined) {
|
|
176
|
+
task.feedbackLink = taskJson.feedback_link;
|
|
177
|
+
}
|
|
178
|
+
if (taskJson.solution_hidden !== undefined) {
|
|
179
|
+
task.solutionHidden = taskJson.solution_hidden;
|
|
180
|
+
}
|
|
181
|
+
if (taskJson.status !== undefined && taskJson.status !== null) {
|
|
182
|
+
const statusMap = {
|
|
183
|
+
Unchecked: CheckStatus_1.CheckStatus.Unchecked,
|
|
184
|
+
Solved: CheckStatus_1.CheckStatus.Solved,
|
|
185
|
+
Failed: CheckStatus_1.CheckStatus.Failed,
|
|
186
|
+
};
|
|
187
|
+
task.status = (_d = statusMap[taskJson.status]) !== null && _d !== void 0 ? _d : CheckStatus_1.CheckStatus.Unchecked;
|
|
188
|
+
}
|
|
189
|
+
if (typeof taskJson.record === "number") {
|
|
190
|
+
task.record = taskJson.record;
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(taskJson.tags)) {
|
|
193
|
+
task.contentTags = taskJson.tags.map(String);
|
|
194
|
+
}
|
|
195
|
+
// Load task files
|
|
196
|
+
const lessonTitle = (_e = lessonJson.title) !== null && _e !== void 0 ? _e : "";
|
|
197
|
+
const files = (_f = taskJson.files) !== null && _f !== void 0 ? _f : {};
|
|
198
|
+
for (const filename of Object.keys(files)) {
|
|
199
|
+
const fileConfig = files[filename];
|
|
200
|
+
const taskFile = yield loadZipTaskFile(filename, fileConfig, task.name, sectionTitle, lessonTitle, zip, aesKey);
|
|
201
|
+
if (taskFile) {
|
|
202
|
+
task.addTaskFileInstance(taskFile);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Hydrate type-specific fields
|
|
206
|
+
if (task instanceof ChoiceTask_1.ChoiceTask) {
|
|
207
|
+
hydrateZipChoiceTask(task, taskJson);
|
|
208
|
+
}
|
|
209
|
+
else if (task instanceof MatchingTask_1.MatchingTask) {
|
|
210
|
+
hydrateZipMatchingTask(task, taskJson);
|
|
211
|
+
}
|
|
212
|
+
else if (task instanceof SortingTask_1.SortingTask) {
|
|
213
|
+
hydrateZipSortingTask(task, taskJson);
|
|
214
|
+
}
|
|
215
|
+
else if (task instanceof TableTask_1.TableTask) {
|
|
216
|
+
hydrateZipTableTask(task, taskJson);
|
|
217
|
+
}
|
|
218
|
+
return task;
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
function createZipTask(taskType) {
|
|
222
|
+
switch (taskType) {
|
|
223
|
+
case "edu":
|
|
224
|
+
case "pycharm":
|
|
225
|
+
return new EduTask_1.EduTask();
|
|
226
|
+
case "code":
|
|
227
|
+
return new CodeTask_1.CodeTask();
|
|
228
|
+
case "number":
|
|
229
|
+
return new NumberTask_1.NumberTask();
|
|
230
|
+
case "string":
|
|
231
|
+
return new StringTask_1.StringTask();
|
|
232
|
+
case "output":
|
|
233
|
+
return new OutputTask_1.OutputTask();
|
|
234
|
+
case "dataset":
|
|
235
|
+
return new DataTask_1.DataTask();
|
|
236
|
+
case "table":
|
|
237
|
+
return new TableTask_1.TableTask();
|
|
238
|
+
case "theory":
|
|
239
|
+
return new TheoryTask_1.TheoryTask();
|
|
240
|
+
case "ide":
|
|
241
|
+
return new IdeTask_1.IdeTask();
|
|
242
|
+
case "unsupported":
|
|
243
|
+
return new UnsupportedTask_1.UnsupportedTask();
|
|
244
|
+
case "remote_edu":
|
|
245
|
+
return new RemoteEduTask_1.RemoteEduTask();
|
|
246
|
+
case "choice":
|
|
247
|
+
return new ChoiceTask_1.ChoiceTask();
|
|
248
|
+
case "matching":
|
|
249
|
+
return new MatchingTask_1.MatchingTask();
|
|
250
|
+
case "sorting":
|
|
251
|
+
return new SortingTask_1.SortingTask();
|
|
252
|
+
default:
|
|
253
|
+
return null;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
// ── Task file loading ────────────────────────────────
|
|
257
|
+
function loadZipTaskFile(filename, config, taskName, sectionTitle, lessonTitle, zip, aesKey) {
|
|
258
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
259
|
+
var _a, _b, _c, _d, _e, _f;
|
|
260
|
+
if (!config.name) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
const taskFile = new TaskFile_1.TaskFile();
|
|
264
|
+
taskFile.name = (_a = config.name) !== null && _a !== void 0 ? _a : filename;
|
|
265
|
+
taskFile.isVisible = (_b = config.is_visible) !== null && _b !== void 0 ? _b : true;
|
|
266
|
+
const isBinary = (_c = config.is_binary) !== null && _c !== void 0 ? _c : false;
|
|
267
|
+
// Read file content from contents/ path
|
|
268
|
+
// Path: contents/{sectionTitle}/{lessonTitle}/{taskName}/{filename}
|
|
269
|
+
const contentPath = buildContentPath(sectionTitle, lessonTitle, taskName, (_d = config.name) !== null && _d !== void 0 ? _d : filename);
|
|
270
|
+
let fileContent;
|
|
271
|
+
if (isBinary) {
|
|
272
|
+
const rawData = zip[contentPath];
|
|
273
|
+
if (rawData) {
|
|
274
|
+
fileContent = bufferToBase64(rawData);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const rawData = zip[contentPath];
|
|
279
|
+
if (rawData) {
|
|
280
|
+
fileContent = new TextDecoder().decode(rawData);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
if (fileContent !== undefined) {
|
|
284
|
+
taskFile.text = fileContent;
|
|
285
|
+
}
|
|
286
|
+
// Handle placeholders
|
|
287
|
+
const placeholders = (_e = config.placeholders) !== null && _e !== void 0 ? _e : [];
|
|
288
|
+
for (const ph of placeholders) {
|
|
289
|
+
if (ph.offset !== undefined && ph.placeholder_text !== undefined) {
|
|
290
|
+
const placeholder = new AnswerPlaceholder_1.AnswerPlaceholder(ph.offset, ph.placeholder_text);
|
|
291
|
+
placeholder.length = (_f = ph.length) !== null && _f !== void 0 ? _f : ph.placeholder_text.length;
|
|
292
|
+
// Decrypt possible_answer if present
|
|
293
|
+
if (ph.possible_answer) {
|
|
294
|
+
const decrypted = yield decryptAesCbc(ph.possible_answer, aesKey);
|
|
295
|
+
if (decrypted !== undefined && decrypted.length > 0) {
|
|
296
|
+
placeholder.possibleAnswer = decrypted;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
placeholder.possibleAnswer = ph.possible_answer;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
taskFile.addAnswerPlaceholder(placeholder);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
return taskFile;
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
function buildContentPath(sectionTitle, lessonTitle, taskName, filename) {
|
|
309
|
+
const parts = ["contents"];
|
|
310
|
+
if (sectionTitle) {
|
|
311
|
+
parts.push(sectionTitle);
|
|
312
|
+
}
|
|
313
|
+
parts.push(lessonTitle);
|
|
314
|
+
parts.push(taskName);
|
|
315
|
+
parts.push(filename);
|
|
316
|
+
return parts.join("/");
|
|
317
|
+
}
|
|
318
|
+
// ── Type-specific hydration ──────────────────────────
|
|
319
|
+
function hydrateZipChoiceTask(task, json) {
|
|
320
|
+
if (json.isMultipleChoice !== undefined) {
|
|
321
|
+
task.isMultipleChoice = json.isMultipleChoice;
|
|
322
|
+
}
|
|
323
|
+
if (json.messageIncorrect !== undefined) {
|
|
324
|
+
task.messageIncorrect = json.messageIncorrect;
|
|
325
|
+
}
|
|
326
|
+
if (Array.isArray(json.choiceOptions)) {
|
|
327
|
+
task.choiceOptions = json.choiceOptions.map((opt) => {
|
|
328
|
+
var _a;
|
|
329
|
+
const option = new ChoiceOption_1.ChoiceOption();
|
|
330
|
+
option.text = (_a = opt.text) !== null && _a !== void 0 ? _a : "";
|
|
331
|
+
if (opt.status === "CORRECT") {
|
|
332
|
+
option.status = ChoiceOptionStatus_1.ChoiceOptionStatus.CORRECT;
|
|
333
|
+
}
|
|
334
|
+
else if (opt.status === "INCORRECT") {
|
|
335
|
+
option.status = ChoiceOptionStatus_1.ChoiceOptionStatus.INCORRECT;
|
|
336
|
+
}
|
|
337
|
+
return option;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
function hydrateZipMatchingTask(task, json) {
|
|
342
|
+
if (Array.isArray(json.captions)) {
|
|
343
|
+
task.captions = json.captions.map((c) => {
|
|
344
|
+
var _a, _b;
|
|
345
|
+
const first = String((_a = c.first) !== null && _a !== void 0 ? _a : "");
|
|
346
|
+
const second = String((_b = c.second) !== null && _b !== void 0 ? _b : "");
|
|
347
|
+
return `${first} : ${second}`;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (Array.isArray(json.options)) {
|
|
351
|
+
task.options = json.options.map(String);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
function hydrateZipSortingTask(task, json) {
|
|
355
|
+
if (Array.isArray(json.options)) {
|
|
356
|
+
task.options = json.options.map(String);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function hydrateZipTableTask(task, json) {
|
|
360
|
+
if (json.isMultipleChoice !== undefined) {
|
|
361
|
+
task.isMultipleChoice = json.isMultipleChoice;
|
|
362
|
+
}
|
|
363
|
+
if (Array.isArray(json.rows)) {
|
|
364
|
+
task.rows = json.rows.map(String);
|
|
365
|
+
}
|
|
366
|
+
if (Array.isArray(json.columns)) {
|
|
367
|
+
task.columns = json.columns.map(String);
|
|
368
|
+
}
|
|
369
|
+
if (Array.isArray(json.selected)) {
|
|
370
|
+
task.selected = json.selected.map((row) => row.map(Boolean));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// ── Additional files ─────────────────────────────────
|
|
374
|
+
function loadZipAdditionalFiles(course, additionalFiles, zip) {
|
|
375
|
+
for (const af of additionalFiles) {
|
|
376
|
+
if (!af.name)
|
|
377
|
+
continue;
|
|
378
|
+
const contentPath = `contents/${af.name}`;
|
|
379
|
+
const rawData = zip[contentPath];
|
|
380
|
+
if (!rawData)
|
|
381
|
+
continue;
|
|
382
|
+
const eduFile = new EduFile_1.EduFile();
|
|
383
|
+
eduFile.name = af.name;
|
|
384
|
+
if (af.is_binary) {
|
|
385
|
+
eduFile.text = bufferToBase64(rawData);
|
|
386
|
+
}
|
|
387
|
+
else {
|
|
388
|
+
eduFile.text = new TextDecoder().decode(rawData);
|
|
389
|
+
}
|
|
390
|
+
course.additionalFiles.push(eduFile);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// ── AES-256-CBC decryption using Web Crypto API ──────
|
|
394
|
+
function decryptAesCbc(encryptedBase64, aesKey) {
|
|
395
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
396
|
+
try {
|
|
397
|
+
const keyBytes = new TextEncoder().encode(aesKey);
|
|
398
|
+
const ivBytes = new TextEncoder().encode(aesKey.slice(0, 16));
|
|
399
|
+
const encryptedBytes = base64ToBuffer(encryptedBase64);
|
|
400
|
+
let cryptoKey;
|
|
401
|
+
try {
|
|
402
|
+
cryptoKey = yield crypto.subtle.importKey("raw", keyBytes, { name: "AES-CBC" }, false, ["decrypt"]);
|
|
403
|
+
}
|
|
404
|
+
catch (_a) {
|
|
405
|
+
// crypto.subtle may not be available
|
|
406
|
+
return undefined;
|
|
407
|
+
}
|
|
408
|
+
const decryptedBuffer = yield crypto.subtle.decrypt({ name: "AES-CBC", iv: ivBytes }, cryptoKey, encryptedBytes.buffer);
|
|
409
|
+
return new TextDecoder().decode(decryptedBuffer);
|
|
410
|
+
}
|
|
411
|
+
catch (_b) {
|
|
412
|
+
return undefined;
|
|
413
|
+
}
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
// ── Utility functions ────────────────────────────────
|
|
417
|
+
function base64ToBuffer(base64) {
|
|
418
|
+
const binaryStr = atob(base64);
|
|
419
|
+
const bytes = new Uint8Array(binaryStr.length);
|
|
420
|
+
for (let i = 0; i < binaryStr.length; i++) {
|
|
421
|
+
bytes[i] = binaryStr.charCodeAt(i);
|
|
422
|
+
}
|
|
423
|
+
return bytes;
|
|
424
|
+
}
|
|
425
|
+
function bufferToBase64(buffer) {
|
|
426
|
+
let binary = "";
|
|
427
|
+
for (let i = 0; i < buffer.length; i++) {
|
|
428
|
+
binary += String.fromCharCode(buffer[i]);
|
|
429
|
+
}
|
|
430
|
+
return btoa(binary);
|
|
431
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
type: marketplace
|
|
2
|
+
title: Example Plain Text course
|
|
3
|
+
language: English
|
|
4
|
+
summary: |-
|
|
5
|
+
This is an introductory Plain text course.
|
|
6
|
+
- The course will be most beneficial for beginner-level Plain text learners.
|
|
7
|
+
- The course covers core Plain text features.
|
|
8
|
+
- It contains hands-on examples of Plain text code implementation and exercises to practice new skills.
|
|
9
|
+
- The course is based on the author's personal experience in Plain text development.
|
|
10
|
+
programming_language: Plain text
|
|
11
|
+
content:
|
|
12
|
+
- lesson1
|
|
13
|
+
mode: Study
|
|
14
|
+
vendor:
|
|
15
|
+
name: Arseniy Pendryak
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
id: 1886105707
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// type your solution here
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type: edu
|
|
2
|
+
files:
|
|
3
|
+
- name: Task.txt
|
|
4
|
+
visible: true
|
|
5
|
+
encrypted_text: J75+qots3TIZHX0wYlhd9I0oXi5xa08uAftxd80chfo=
|
|
6
|
+
learner_created: false
|
|
7
|
+
- name: tests/Tests.txt
|
|
8
|
+
visible: false
|
|
9
|
+
encrypted_text: PwG9zjgI55k/oP/VCAQx/NEJ0ve6YhbcA+S5bJq8F9E=
|
|
10
|
+
learner_created: false
|
|
11
|
+
status: Unchecked
|
|
12
|
+
record: -1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
id: 1663033322
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
This is a task description file.
|
|
2
|
+
Its content will be displayed to a learner
|
|
3
|
+
in the **Task Description** window.
|
|
4
|
+
|
|
5
|
+
It supports both Markdown and HTML.
|
|
6
|
+
To toggle the format, you can rename **task.md**
|
|
7
|
+
to **task.html**, or vice versa.
|
|
8
|
+
The default task description format can be changed
|
|
9
|
+
in **Preferences | Tools | Education**,
|
|
10
|
+
but this will not affect any existing task description files.
|
|
11
|
+
|
|
12
|
+
The following features are available in
|
|
13
|
+
**task.md/task.html** which are specific to the JetBrains Academy plugin:
|
|
14
|
+
|
|
15
|
+
- Hints can be added anywhere in the task text.
|
|
16
|
+
Type "hint" and press Tab.
|
|
17
|
+
Hints should be added to an empty line in the task text.
|
|
18
|
+
In hints you can use both HTML and Markdown.
|
|
19
|
+
<div class="hint">
|
|
20
|
+
|
|
21
|
+
Text of your hint
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
- You may need to refer your learners to a particular lesson,
|
|
26
|
+
task, or file. To achieve this, you can use the in-course links.
|
|
27
|
+
Specify the path using the `[link_text](course://lesson1/task1/file1)` format.
|
|
28
|
+
|
|
29
|
+
- You can insert shortcuts in the task description.
|
|
30
|
+
While **task.html/task.md** is open, right-click anywhere
|
|
31
|
+
on the **Editor** tab and choose the **Insert shortcut** option
|
|
32
|
+
from the context menu.
|
|
33
|
+
For example: &shortcut:FileStructurePopup;.
|
|
34
|
+
|
|
35
|
+
- Insert the %`IDE_NAME`% macro,
|
|
36
|
+
which will be replaced by the actual IDE name.
|
|
37
|
+
For example, **%IDE_NAME%**.
|
|
38
|
+
|
|
39
|
+
- Insert PSI elements, by using links like
|
|
40
|
+
`[element_description](psi_element://link.to.element)`.
|
|
41
|
+
To get such a link, right-click the class or method
|
|
42
|
+
and select **Copy Reference**.
|
|
43
|
+
Then press &shortcut:EditorPaste; to insert the link where appropriate.
|
|
44
|
+
For example, a [link to the "contains" method](psi_element://java.lang.String#contains).
|
|
45
|
+
|
|
46
|
+
- You can add link to file using **full path** like this:
|
|
47
|
+
`[file_link](file://lesson1/task1/file.txt)`.
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// type your solution here
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
type: edu
|
|
2
|
+
files:
|
|
3
|
+
- name: Task.txt
|
|
4
|
+
visible: true
|
|
5
|
+
encrypted_text: J75+qots3TIZHX0wYlhd9I0oXi5xa08uAftxd80chfo=
|
|
6
|
+
learner_created: false
|
|
7
|
+
- name: tests/Tests.txt
|
|
8
|
+
visible: false
|
|
9
|
+
encrypted_text: PwG9zjgI55k/oP/VCAQx/NEJ0ve6YhbcA+S5bJq8F9E=
|
|
10
|
+
learner_created: false
|
|
11
|
+
status: Unchecked
|
|
12
|
+
record: -1
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
id: 1213512926
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
This is a task description file.
|
|
2
|
+
Its content will be displayed to a learner
|
|
3
|
+
in the **Task Description** window.
|
|
4
|
+
|
|
5
|
+
It supports both Markdown and HTML.
|
|
6
|
+
To toggle the format, you can rename **task.md**
|
|
7
|
+
to **task.html**, or vice versa.
|
|
8
|
+
The default task description format can be changed
|
|
9
|
+
in **Preferences | Tools | Education**,
|
|
10
|
+
but this will not affect any existing task description files.
|
|
11
|
+
|
|
12
|
+
The following features are available in
|
|
13
|
+
**task.md/task.html** which are specific to the JetBrains Academy plugin:
|
|
14
|
+
|
|
15
|
+
- Hints can be added anywhere in the task text.
|
|
16
|
+
Type "hint" and press Tab.
|
|
17
|
+
Hints should be added to an empty line in the task text.
|
|
18
|
+
In hints you can use both HTML and Markdown.
|
|
19
|
+
<div class="hint">
|
|
20
|
+
|
|
21
|
+
Text of your hint
|
|
22
|
+
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
- You may need to refer your learners to a particular lesson,
|
|
26
|
+
task, or file. To achieve this, you can use the in-course links.
|
|
27
|
+
Specify the path using the `[link_text](course://lesson1/task1/file1)` format.
|
|
28
|
+
|
|
29
|
+
- You can insert shortcuts in the task description.
|
|
30
|
+
While **task.html/task.md** is open, right-click anywhere
|
|
31
|
+
on the **Editor** tab and choose the **Insert shortcut** option
|
|
32
|
+
from the context menu.
|
|
33
|
+
For example: &shortcut:FileStructurePopup;.
|
|
34
|
+
|
|
35
|
+
- Insert the %`IDE_NAME`% macro,
|
|
36
|
+
which will be replaced by the actual IDE name.
|
|
37
|
+
For example, **%IDE_NAME%**.
|
|
38
|
+
|
|
39
|
+
- Insert PSI elements, by using links like
|
|
40
|
+
`[element_description](psi_element://link.to.element)`.
|
|
41
|
+
To get such a link, right-click the class or method
|
|
42
|
+
and select **Copy Reference**.
|
|
43
|
+
Then press &shortcut:EditorPaste; to insert the link where appropriate.
|
|
44
|
+
For example, a [link to the "contains" method](psi_element://java.lang.String#contains).
|
|
45
|
+
|
|
46
|
+
- You can add link to file using **full path** like this:
|
|
47
|
+
`[file_link](file://lesson1/task1/file.txt)`.
|
|
File without changes
|
package/package.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "course-format-ts",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "tsc",
|
|
8
|
+
"test": "npm run build && node --test"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"fflate": "^0.8.3",
|
|
12
|
+
"mime-types": "^2.1.35",
|
|
13
|
+
"yaml": "^2.5.0"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^22.12.0",
|
|
17
|
+
"typescript": "^5.5.3"
|
|
18
|
+
}
|
|
19
|
+
}
|