@learnpack/learnpack 5.0.275 → 5.0.277

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.
Files changed (95) hide show
  1. package/README.md +409 -409
  2. package/lib/commands/audit.js +15 -15
  3. package/lib/commands/breakToken.js +19 -19
  4. package/lib/commands/clean.js +3 -3
  5. package/lib/commands/init.js +41 -41
  6. package/lib/commands/logout.js +3 -3
  7. package/lib/commands/publish.js +5 -10
  8. package/lib/commands/serve.js +55 -2
  9. package/lib/creatorDist/assets/index-BfLyIQVh.js +10343 -10224
  10. package/lib/managers/config/index.js +77 -77
  11. package/lib/utils/api.d.ts +1 -1
  12. package/lib/utils/api.js +12 -9
  13. package/lib/utils/creatorUtilities.js +14 -14
  14. package/lib/utils/export/epub.d.ts +2 -0
  15. package/lib/utils/export/epub.js +298 -0
  16. package/lib/utils/export/index.d.ts +3 -0
  17. package/lib/utils/export/index.js +7 -0
  18. package/lib/utils/export/scorm.d.ts +2 -0
  19. package/lib/utils/export/scorm.js +84 -0
  20. package/lib/utils/export/shared.d.ts +4 -0
  21. package/lib/utils/export/shared.js +61 -0
  22. package/lib/utils/export/types.d.ts +15 -0
  23. package/lib/utils/export/types.js +2 -0
  24. package/package.json +2 -1
  25. package/src/commands/audit.ts +487 -487
  26. package/src/commands/breakToken.ts +67 -67
  27. package/src/commands/clean.ts +30 -30
  28. package/src/commands/init.ts +650 -650
  29. package/src/commands/logout.ts +38 -38
  30. package/src/commands/publish.ts +20 -25
  31. package/src/commands/serve.ts +69 -4
  32. package/src/commands/start.ts +333 -333
  33. package/src/commands/translate.ts +123 -123
  34. package/src/creator/README.md +54 -54
  35. package/src/creator/eslint.config.js +7 -7
  36. package/src/creator/src/components/syllabus/ContentIndex.tsx +312 -312
  37. package/src/creator/src/i18n.ts +28 -28
  38. package/src/creator/src/index.css +217 -217
  39. package/src/creator/src/locales/en.json +126 -126
  40. package/src/creator/src/locales/es.json +126 -126
  41. package/src/creator/src/utils/configTypes.ts +122 -122
  42. package/src/creator/src/utils/constants.ts +13 -13
  43. package/src/creator/src/utils/creatorUtils.ts +46 -46
  44. package/src/creator/src/utils/eventBus.ts +2 -2
  45. package/src/creator/src/utils/lib.ts +468 -468
  46. package/src/creator/src/utils/socket.ts +61 -61
  47. package/src/creator/src/utils/store.ts +222 -222
  48. package/src/creator/src/vite-env.d.ts +1 -1
  49. package/src/creator/vite.config.ts +13 -13
  50. package/src/creatorDist/assets/index-BfLyIQVh.js +10343 -10224
  51. package/src/managers/config/defaults.ts +49 -49
  52. package/src/managers/config/exercise.ts +364 -364
  53. package/src/managers/config/index.ts +775 -775
  54. package/src/managers/file.ts +236 -236
  55. package/src/managers/server/routes.ts +554 -554
  56. package/src/managers/session.ts +182 -182
  57. package/src/managers/telemetry.ts +188 -188
  58. package/src/models/action.ts +13 -13
  59. package/src/models/config-manager.ts +28 -28
  60. package/src/models/config.ts +106 -106
  61. package/src/models/creator.ts +47 -47
  62. package/src/models/exercise-obj.ts +30 -30
  63. package/src/models/session.ts +39 -39
  64. package/src/models/socket.ts +61 -61
  65. package/src/models/status.ts +16 -16
  66. package/src/ui/_app/app.css +1 -1
  67. package/src/ui/_app/app.js +400 -397
  68. package/src/ui/app.tar.gz +0 -0
  69. package/src/utils/BaseCommand.ts +56 -56
  70. package/src/utils/api.ts +53 -39
  71. package/src/utils/audit.ts +392 -392
  72. package/src/utils/checkNotInstalled.ts +267 -267
  73. package/src/utils/configBuilder.ts +82 -82
  74. package/src/utils/convertCreds.js +34 -34
  75. package/src/utils/creatorUtilities.ts +504 -504
  76. package/src/utils/export/README.md +178 -0
  77. package/src/utils/export/epub.ts +400 -0
  78. package/src/utils/export/index.ts +3 -0
  79. package/src/utils/export/scorm.ts +121 -0
  80. package/src/utils/export/shared.ts +61 -0
  81. package/src/utils/export/types.ts +17 -0
  82. package/src/utils/incrementVersion.js +74 -74
  83. package/src/utils/misc.ts +58 -58
  84. package/src/utils/rigoActions.ts +500 -500
  85. package/src/utils/sidebarGenerator.ts +195 -195
  86. package/src/utils/templates/epub/epub.css +133 -0
  87. package/src/utils/templates/isolated/exercises/01-hello-world/README.es.md +26 -26
  88. package/src/utils/templates/isolated/exercises/01-hello-world/README.md +26 -26
  89. package/src/utils/templates/scorm/adlcp_rootv1p2.xsd +110 -0
  90. package/src/utils/templates/scorm/config/api.js +175 -0
  91. package/src/utils/templates/scorm/config/index.html +210 -0
  92. package/src/utils/templates/scorm/ims_xml.xsd +1 -0
  93. package/src/utils/templates/scorm/imscp_rootv1p1p2.xsd +345 -0
  94. package/src/utils/templates/scorm/imsmanifest.xml +38 -0
  95. package/src/utils/templates/scorm/imsmd_rootv1p2p1.xsd +573 -0
@@ -1,195 +1,195 @@
1
- import path = require("path")
2
-
3
- import Console from "./console"
4
- import fs = require("fs")
5
- import { IExercise, IExerciseData } from "../models/exercise-obj"
6
- import { IConfigObj } from "../models/config"
7
- import SessionManager from "../managers/session"
8
- import { fillSidebarJSON } from "./rigoActions"
9
-
10
- type TTitleTranslations = {
11
- [key: string]: string
12
- }
13
-
14
- export type TSidebar = {
15
- [key: string]: TTitleTranslations
16
- }
17
-
18
- export const generateSidebar = (exercises: IExercise[], learnPath: string) => {
19
- const sidebarPath = path.join(learnPath, "sidebar.json")
20
- let sidebar: TSidebar = {}
21
- if (fs.existsSync(sidebarPath)) {
22
- sidebar = JSON.parse(fs.readFileSync(sidebarPath, "utf8"))
23
- }
24
-
25
- for (const exercise of exercises) {
26
- sidebar[exercise.slug] = {
27
- ...sidebar[exercise.slug],
28
- us: exercise.title,
29
- }
30
- }
31
-
32
- fs.writeFileSync(sidebarPath, JSON.stringify(sidebar, null, 2))
33
-
34
- return sidebar
35
- }
36
-
37
- export const addExerciseToSidebar = (
38
- exerciseSlug: string,
39
- targetLanguage: string,
40
- translatedSlug: string,
41
- learnPath: string
42
- ) => {
43
- const sidebarPath = path.join(learnPath, "sidebar.json")
44
- let sidebar: TSidebar = {}
45
- if (fs.existsSync(sidebarPath)) {
46
- sidebar = JSON.parse(fs.readFileSync(sidebarPath, "utf8"))
47
- }
48
-
49
- sidebar[exerciseSlug] = {
50
- ...sidebar[exerciseSlug],
51
- [targetLanguage]: translatedSlug,
52
- }
53
-
54
- fs.writeFileSync(sidebarPath, JSON.stringify(sidebar, null, 2))
55
-
56
- return sidebar
57
- }
58
-
59
- export const checkAndFixSidebar = async (
60
- configObj: IConfigObj,
61
- autoFix = false
62
- ): Promise<boolean> => {
63
- if (
64
- configObj.config &&
65
- fs.existsSync(configObj.config.dirPath + "/sidebar.json")
66
- ) {
67
- let hasErrors = false
68
- const sidebar = fs.readFileSync(
69
- configObj.config.dirPath + "/sidebar.json",
70
- "utf8"
71
- )
72
- // parse the sidebar.json file
73
- const sidebarJson = JSON.parse(sidebar)
74
-
75
- const exerciseTranslations: Set<string> = new Set()
76
- configObj.exercises?.map(e =>
77
- // eslint-disable-next-line
78
- Object.keys((e.translations || {}) as any).forEach((t) =>
79
- exerciseTranslations.add(t)
80
- )
81
- )
82
- // Validation
83
- for (const [key, value] of Object.entries(sidebarJson) as [
84
- string,
85
- TTitleTranslations
86
- ][]) {
87
- for (const lang of exerciseTranslations) {
88
- if (!Object.prototype.hasOwnProperty.call(value, lang)) {
89
- hasErrors = true
90
- }
91
- }
92
- }
93
-
94
- if (hasErrors && autoFix) {
95
- Console.warning("Filling sidebar.json file with missing translations")
96
- const sessionPayload = await SessionManager.getPayload()
97
-
98
- const rigoToken = sessionPayload.rigobot.key
99
-
100
- if (!rigoToken) {
101
- Console.error("No Rigobot token found, please login first!")
102
- return false
103
- }
104
-
105
- const response = await fillSidebarJSON(rigoToken, {
106
- needed_translations: JSON.stringify(exerciseTranslations),
107
- sidebar_json: JSON.stringify(sidebarJson),
108
- })
109
-
110
- const newSidebarJson = JSON.parse(response.parsed.new_sidebar_file)
111
- fs.writeFileSync(
112
- configObj.config.dirPath + "/sidebar.json",
113
- JSON.stringify(newSidebarJson, null, 4)
114
- )
115
- Console.info("Sidebar.json was filled with missing translations")
116
- return true
117
- }
118
-
119
- if (hasErrors && !autoFix) {
120
- return false
121
- }
122
-
123
- return true
124
- }
125
-
126
- return false
127
- }
128
-
129
- export const checkAndFixSidebarPure = async (
130
- sidebarJson: Record<string, Record<string, string>>,
131
- exercises: any[],
132
- rigoToken: string,
133
- autoFix = true
134
- ): Promise<{
135
- valid: boolean
136
- fixedSidebar?: Record<string, Record<string, string>>
137
- }> => {
138
- const exerciseTranslations: Set<string> = new Set()
139
- for (const e of exercises) {
140
- for (const lang of Object.keys(e.translations || {})) {
141
- exerciseTranslations.add(lang)
142
- }
143
- }
144
-
145
- let hasErrors = false
146
- const fixedSidebar: Record<string, Record<string, string>> = {
147
- ...sidebarJson,
148
- }
149
-
150
- for (const exercise of exercises) {
151
- const slug = exercise.slug
152
- const existingEntry = fixedSidebar[slug]
153
-
154
- if (!existingEntry) {
155
- hasErrors = true
156
- fixedSidebar[slug] = { us: exercise.slug }
157
- continue
158
- }
159
-
160
- for (const lang of exerciseTranslations) {
161
- if (!Object.prototype.hasOwnProperty.call(existingEntry, lang)) {
162
- hasErrors = true
163
- }
164
- }
165
- }
166
-
167
- if (hasErrors && autoFix) {
168
- if (!rigoToken) {
169
- Console.error("No Rigobot token provided!")
170
- return { valid: false }
171
- }
172
-
173
- Console.warning(
174
- "Filling sidebar JSON with missing translations or exercises"
175
- )
176
-
177
- const response = await fillSidebarJSON(rigoToken, {
178
- needed_translations: JSON.stringify([...exerciseTranslations]),
179
- sidebar_json: JSON.stringify(fixedSidebar),
180
- })
181
-
182
- const newSidebarJson = JSON.parse(response.parsed.new_sidebar_file)
183
-
184
- Console.info("Sidebar JSON was fixed")
185
- return {
186
- valid: true,
187
- fixedSidebar: newSidebarJson,
188
- }
189
- }
190
-
191
- return {
192
- valid: !hasErrors,
193
- fixedSidebar: hasErrors ? fixedSidebar : undefined,
194
- }
195
- }
1
+ import path = require("path")
2
+
3
+ import Console from "./console"
4
+ import fs = require("fs")
5
+ import { IExercise, IExerciseData } from "../models/exercise-obj"
6
+ import { IConfigObj } from "../models/config"
7
+ import SessionManager from "../managers/session"
8
+ import { fillSidebarJSON } from "./rigoActions"
9
+
10
+ type TTitleTranslations = {
11
+ [key: string]: string
12
+ }
13
+
14
+ export type TSidebar = {
15
+ [key: string]: TTitleTranslations
16
+ }
17
+
18
+ export const generateSidebar = (exercises: IExercise[], learnPath: string) => {
19
+ const sidebarPath = path.join(learnPath, "sidebar.json")
20
+ let sidebar: TSidebar = {}
21
+ if (fs.existsSync(sidebarPath)) {
22
+ sidebar = JSON.parse(fs.readFileSync(sidebarPath, "utf8"))
23
+ }
24
+
25
+ for (const exercise of exercises) {
26
+ sidebar[exercise.slug] = {
27
+ ...sidebar[exercise.slug],
28
+ us: exercise.title,
29
+ }
30
+ }
31
+
32
+ fs.writeFileSync(sidebarPath, JSON.stringify(sidebar, null, 2))
33
+
34
+ return sidebar
35
+ }
36
+
37
+ export const addExerciseToSidebar = (
38
+ exerciseSlug: string,
39
+ targetLanguage: string,
40
+ translatedSlug: string,
41
+ learnPath: string
42
+ ) => {
43
+ const sidebarPath = path.join(learnPath, "sidebar.json")
44
+ let sidebar: TSidebar = {}
45
+ if (fs.existsSync(sidebarPath)) {
46
+ sidebar = JSON.parse(fs.readFileSync(sidebarPath, "utf8"))
47
+ }
48
+
49
+ sidebar[exerciseSlug] = {
50
+ ...sidebar[exerciseSlug],
51
+ [targetLanguage]: translatedSlug,
52
+ }
53
+
54
+ fs.writeFileSync(sidebarPath, JSON.stringify(sidebar, null, 2))
55
+
56
+ return sidebar
57
+ }
58
+
59
+ export const checkAndFixSidebar = async (
60
+ configObj: IConfigObj,
61
+ autoFix = false
62
+ ): Promise<boolean> => {
63
+ if (
64
+ configObj.config &&
65
+ fs.existsSync(configObj.config.dirPath + "/sidebar.json")
66
+ ) {
67
+ let hasErrors = false
68
+ const sidebar = fs.readFileSync(
69
+ configObj.config.dirPath + "/sidebar.json",
70
+ "utf8"
71
+ )
72
+ // parse the sidebar.json file
73
+ const sidebarJson = JSON.parse(sidebar)
74
+
75
+ const exerciseTranslations: Set<string> = new Set()
76
+ configObj.exercises?.map(e =>
77
+ // eslint-disable-next-line
78
+ Object.keys((e.translations || {}) as any).forEach((t) =>
79
+ exerciseTranslations.add(t)
80
+ )
81
+ )
82
+ // Validation
83
+ for (const [key, value] of Object.entries(sidebarJson) as [
84
+ string,
85
+ TTitleTranslations
86
+ ][]) {
87
+ for (const lang of exerciseTranslations) {
88
+ if (!Object.prototype.hasOwnProperty.call(value, lang)) {
89
+ hasErrors = true
90
+ }
91
+ }
92
+ }
93
+
94
+ if (hasErrors && autoFix) {
95
+ Console.warning("Filling sidebar.json file with missing translations")
96
+ const sessionPayload = await SessionManager.getPayload()
97
+
98
+ const rigoToken = sessionPayload.rigobot.key
99
+
100
+ if (!rigoToken) {
101
+ Console.error("No Rigobot token found, please login first!")
102
+ return false
103
+ }
104
+
105
+ const response = await fillSidebarJSON(rigoToken, {
106
+ needed_translations: JSON.stringify(exerciseTranslations),
107
+ sidebar_json: JSON.stringify(sidebarJson),
108
+ })
109
+
110
+ const newSidebarJson = JSON.parse(response.parsed.new_sidebar_file)
111
+ fs.writeFileSync(
112
+ configObj.config.dirPath + "/sidebar.json",
113
+ JSON.stringify(newSidebarJson, null, 4)
114
+ )
115
+ Console.info("Sidebar.json was filled with missing translations")
116
+ return true
117
+ }
118
+
119
+ if (hasErrors && !autoFix) {
120
+ return false
121
+ }
122
+
123
+ return true
124
+ }
125
+
126
+ return false
127
+ }
128
+
129
+ export const checkAndFixSidebarPure = async (
130
+ sidebarJson: Record<string, Record<string, string>>,
131
+ exercises: any[],
132
+ rigoToken: string,
133
+ autoFix = true
134
+ ): Promise<{
135
+ valid: boolean
136
+ fixedSidebar?: Record<string, Record<string, string>>
137
+ }> => {
138
+ const exerciseTranslations: Set<string> = new Set()
139
+ for (const e of exercises) {
140
+ for (const lang of Object.keys(e.translations || {})) {
141
+ exerciseTranslations.add(lang)
142
+ }
143
+ }
144
+
145
+ let hasErrors = false
146
+ const fixedSidebar: Record<string, Record<string, string>> = {
147
+ ...sidebarJson,
148
+ }
149
+
150
+ for (const exercise of exercises) {
151
+ const slug = exercise.slug
152
+ const existingEntry = fixedSidebar[slug]
153
+
154
+ if (!existingEntry) {
155
+ hasErrors = true
156
+ fixedSidebar[slug] = { us: exercise.slug }
157
+ continue
158
+ }
159
+
160
+ for (const lang of exerciseTranslations) {
161
+ if (!Object.prototype.hasOwnProperty.call(existingEntry, lang)) {
162
+ hasErrors = true
163
+ }
164
+ }
165
+ }
166
+
167
+ if (hasErrors && autoFix) {
168
+ if (!rigoToken) {
169
+ Console.error("No Rigobot token provided!")
170
+ return { valid: false }
171
+ }
172
+
173
+ Console.warning(
174
+ "Filling sidebar JSON with missing translations or exercises"
175
+ )
176
+
177
+ const response = await fillSidebarJSON(rigoToken, {
178
+ needed_translations: JSON.stringify([...exerciseTranslations]),
179
+ sidebar_json: JSON.stringify(fixedSidebar),
180
+ })
181
+
182
+ const newSidebarJson = JSON.parse(response.parsed.new_sidebar_file)
183
+
184
+ Console.info("Sidebar JSON was fixed")
185
+ return {
186
+ valid: true,
187
+ fixedSidebar: newSidebarJson,
188
+ }
189
+ }
190
+
191
+ return {
192
+ valid: !hasErrors,
193
+ fixedSidebar: hasErrors ? fixedSidebar : undefined,
194
+ }
195
+ }
@@ -0,0 +1,133 @@
1
+ /* Basic EPUB styling */
2
+ body {
3
+ font-family: "Georgia", serif;
4
+ line-height: 1.6;
5
+ margin: 2em;
6
+ color: #333;
7
+ }
8
+
9
+ h1,
10
+ h2,
11
+ h3,
12
+ h4,
13
+ h5,
14
+ h6 {
15
+ color: #2c3e50;
16
+ margin-top: 1.5em;
17
+ margin-bottom: 0.5em;
18
+ }
19
+
20
+ h1 {
21
+ font-size: 2em;
22
+ border-bottom: 2px solid #3498db;
23
+ padding-bottom: 0.3em;
24
+ }
25
+
26
+ h2 {
27
+ font-size: 1.5em;
28
+ border-bottom: 1px solid #bdc3c7;
29
+ padding-bottom: 0.2em;
30
+ }
31
+
32
+ p {
33
+ margin-bottom: 1em;
34
+ text-align: justify;
35
+ }
36
+
37
+ code {
38
+ background-color: #f8f9fa;
39
+ padding: 0.2em 0.4em;
40
+ border-radius: 3px;
41
+ font-family: "Courier New", monospace;
42
+ font-size: 0.9em;
43
+ }
44
+
45
+ pre {
46
+ background-color: #f8f9fa;
47
+ padding: 1em;
48
+ border-radius: 5px;
49
+ overflow-x: auto;
50
+ border-left: 4px solid #3498db;
51
+ }
52
+
53
+ pre code {
54
+ background-color: transparent;
55
+ padding: 0;
56
+ }
57
+
58
+ blockquote {
59
+ border-left: 4px solid #bdc3c7;
60
+ margin: 1em 0;
61
+ padding-left: 1em;
62
+ font-style: italic;
63
+ color: #7f8c8d;
64
+ }
65
+
66
+ ul,
67
+ ol {
68
+ margin-bottom: 1em;
69
+ padding-left: 2em;
70
+ }
71
+
72
+ li {
73
+ margin-bottom: 0.5em;
74
+ }
75
+
76
+ a {
77
+ color: #3498db;
78
+ text-decoration: none;
79
+ }
80
+
81
+ a:hover {
82
+ text-decoration: underline;
83
+ }
84
+
85
+ img {
86
+ max-width: 100%;
87
+ height: auto;
88
+ display: block;
89
+ margin: 1em auto;
90
+ }
91
+
92
+ table {
93
+ border-collapse: collapse;
94
+ width: 100%;
95
+ margin: 1em 0;
96
+ }
97
+
98
+ th,
99
+ td {
100
+ border: 1px solid #ddd;
101
+ padding: 8px;
102
+ text-align: left;
103
+ }
104
+
105
+ th {
106
+ background-color: #f2f2f2;
107
+ font-weight: bold;
108
+ }
109
+
110
+ #toc {
111
+ background-color: #f8f9fa;
112
+ padding: 1em;
113
+ border-radius: 5px;
114
+ margin-bottom: 2em;
115
+ }
116
+
117
+ #toc h2 {
118
+ border-bottom: none;
119
+ margin-top: 0;
120
+ }
121
+
122
+ #toc ol {
123
+ padding-left: 1em;
124
+ }
125
+
126
+ #toc li {
127
+ margin-bottom: 0.3em;
128
+ }
129
+
130
+ #toc a {
131
+ color: #2c3e50;
132
+ font-weight: 500;
133
+ }
@@ -1,26 +1,26 @@
1
- # `01` Primer Ejercicio
2
-
3
- Hemos creado este primer ejercicio como ejemplo. Lo puedes ubicar en la carpeta `./01-hello-world`.
4
-
5
- 1. Cada ejercicio debe estar ubicado en carpetas separadas y debe tener un archivo README.md con las instrucciones del ejercicio escrito en markdown.
6
- 2. Puedes tener un archivo README el cual será como una página de un libro, sin archivos de código.
7
- 3. También puedes agregar un archivo `README.[lenguaje].md` para traducciones, por ejemplo `README.es.md` para español.
8
-
9
- ## Inserta videos
10
-
11
- Si quieres incluir algún video introductorio para cada ejercicio, agrega la propiedad `intro` en el inicio del README.md para ese ejercicio en particular:
12
-
13
- ```markdown
14
- ---
15
- intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
16
- ---
17
- ```
18
-
19
- Tambien puedes agregar un video explicando la solución para cada ejercicio agregando la propiedad `tutorial` al inicio del markdown del README.md correspondiente:
20
-
21
- ```markdown
22
- ---
23
- intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
24
- tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
25
- ---
26
- ```
1
+ # `01` Primer Ejercicio
2
+
3
+ Hemos creado este primer ejercicio como ejemplo. Lo puedes ubicar en la carpeta `./01-hello-world`.
4
+
5
+ 1. Cada ejercicio debe estar ubicado en carpetas separadas y debe tener un archivo README.md con las instrucciones del ejercicio escrito en markdown.
6
+ 2. Puedes tener un archivo README el cual será como una página de un libro, sin archivos de código.
7
+ 3. También puedes agregar un archivo `README.[lenguaje].md` para traducciones, por ejemplo `README.es.md` para español.
8
+
9
+ ## Inserta videos
10
+
11
+ Si quieres incluir algún video introductorio para cada ejercicio, agrega la propiedad `intro` en el inicio del README.md para ese ejercicio en particular:
12
+
13
+ ```markdown
14
+ ---
15
+ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
16
+ ---
17
+ ```
18
+
19
+ Tambien puedes agregar un video explicando la solución para cada ejercicio agregando la propiedad `tutorial` al inicio del markdown del README.md correspondiente:
20
+
21
+ ```markdown
22
+ ---
23
+ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
24
+ tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
25
+ ---
26
+ ```
@@ -1,26 +1,26 @@
1
- # `01` First Exercise
2
-
3
- We created this first exercise as an example, you can find it located in the folder `./01-hello-world`.
4
-
5
- 1. Every exercise must be located on a separate folder and it must have a README.md file inside with the exercise instructions written in markdown.
6
- 2. You can have just a README file and it will be like a page in a book, no code files.
7
- 3. You can also add a `README.[lang].md` file for translations, for example: `README.es.md` for spanish.
8
-
9
- ## Video compatibility
10
-
11
- If you want to include some video introduction for each exercise, add a `intro` property in the markdown frontmatter of the README.md for that particular exercise:
12
-
13
- ```markdown
14
- ---
15
- intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
16
- ---
17
- ```
18
-
19
- You can also add a video solution for each exercise by adding a `tutorial` property on the markdown frontmatter of it's README.md:
20
-
21
- ```markdown
22
- ---
23
- intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
24
- tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
25
- ---
26
- ```
1
+ # `01` First Exercise
2
+
3
+ We created this first exercise as an example, you can find it located in the folder `./01-hello-world`.
4
+
5
+ 1. Every exercise must be located on a separate folder and it must have a README.md file inside with the exercise instructions written in markdown.
6
+ 2. You can have just a README file and it will be like a page in a book, no code files.
7
+ 3. You can also add a `README.[lang].md` file for translations, for example: `README.es.md` for spanish.
8
+
9
+ ## Video compatibility
10
+
11
+ If you want to include some video introduction for each exercise, add a `intro` property in the markdown frontmatter of the README.md for that particular exercise:
12
+
13
+ ```markdown
14
+ ---
15
+ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
16
+ ---
17
+ ```
18
+
19
+ You can also add a video solution for each exercise by adding a `tutorial` property on the markdown frontmatter of it's README.md:
20
+
21
+ ```markdown
22
+ ---
23
+ intro: "https://www.youtube.com/watch?v=YkgkThdzX-8"
24
+ tutorial: "https://www.youtube.com/watch?v=YkgkThdzX-8"
25
+ ---
26
+ ```