cyber-elx 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/DEV_DOC.md +567 -0
- package/ELX_PAGES.md +186 -0
- package/README.md +92 -0
- package/bin/cyber-elx.js +3 -0
- package/package.json +27 -0
- package/src/api.js +35 -0
- package/src/cache.js +53 -0
- package/src/config.js +57 -0
- package/src/files.js +114 -0
- package/src/index.js +314 -0
- package/src/prompts.js +70 -0
package/DEV_DOC.md
ADDED
|
@@ -0,0 +1,567 @@
|
|
|
1
|
+
# CyberOcean Custom Elx Theme
|
|
2
|
+
|
|
3
|
+
## Steps
|
|
4
|
+
|
|
5
|
+
1. Make a detailled plan of the website
|
|
6
|
+
- Layout
|
|
7
|
+
- List all the pages
|
|
8
|
+
- List all the sections
|
|
9
|
+
- Header & Footer
|
|
10
|
+
- Which available variables are going to be used
|
|
11
|
+
- Confirm with the user
|
|
12
|
+
+ Give the user the plan of the pages
|
|
13
|
+
+ Ask for images/icons if you need (The user can upload files from the admin dashboard and give you back the links)
|
|
14
|
+
+ Ask about the language to use
|
|
15
|
+
+ Ask about the colors to use
|
|
16
|
+
2. Start by creating the layout
|
|
17
|
+
- Create the `layouts/theme.liquid` file
|
|
18
|
+
- Create the `sections/header.liquid` file
|
|
19
|
+
- Create the `sections/footer.liquid` file
|
|
20
|
+
- **Important**: If there will be modifications on the existing, then always start by creating a fresh copy from the default files, and then modify it
|
|
21
|
+
3. Create the rest of the pages (With the sections IN CASE YOU USED THEM)
|
|
22
|
+
- Create the `templates/home_page.liquid`
|
|
23
|
+
- Create the `templates/course_page.liquid`
|
|
24
|
+
- Create the `templates/about_page.liquid`
|
|
25
|
+
- Create the `templates/category_page.liquid`
|
|
26
|
+
- Create the `templates/courses_page.liquid`
|
|
27
|
+
- Create the `templates/contact_page.liquid`
|
|
28
|
+
- Create the `templates/blogs_page.liquid`
|
|
29
|
+
- Create the `templates/blog_page.liquid`
|
|
30
|
+
- Rules:
|
|
31
|
+
+ **Do not** use translation unless asked for it
|
|
32
|
+
+ **Do not** try to always to copy paste the default files, use them as a reference, unless asked for it
|
|
33
|
+
+ **Do not** try to always use all available variables, use only the variables you need, unless asked for it
|
|
34
|
+
+ **Always** make your design mobile responsive
|
|
35
|
+
+ **Always** keep sections, templates and layouts file names simple
|
|
36
|
+
> Correct: `sections/header.liquid`
|
|
37
|
+
> Wrong: `sections/header_custom_elx.liquid`
|
|
38
|
+
|
|
39
|
+
## Defaults
|
|
40
|
+
|
|
41
|
+
The default files are stored in the `defaults` folder
|
|
42
|
+
- The files in `defaults` folder are read-only
|
|
43
|
+
- Their purpose is to provide a reference when creating custom files
|
|
44
|
+
- The files in `defaults` folder are created using an old template
|
|
45
|
+
- The files in `defaults` folder use css/js/media files from the server, you can use them too, if needed
|
|
46
|
+
- The files in `defaults` folder are going to be used by the server if you don't provide custom files, example: if you don't provide a custom `layouts/theme.liquid`, the server will use the default one from `defaults/layouts/theme.liquid`
|
|
47
|
+
|
|
48
|
+
## Customization
|
|
49
|
+
|
|
50
|
+
- You are not forced to use all the available variables in the templates/sections/layouts, you can use only the variables you need
|
|
51
|
+
|
|
52
|
+
### Layout
|
|
53
|
+
|
|
54
|
+
- `layouts/theme.liquid` is the main layout file of the website
|
|
55
|
+
- By editing it, you can modify the layout of the website
|
|
56
|
+
- To override the default layout's header or footer, you need to:
|
|
57
|
+
1. Create a custom file in `sections` folder (example: `sections/header.liquid` or `sections/footer.liquid`)
|
|
58
|
+
2. Use the `section` tag in `layouts/theme.liquid` to include the custom section, like this:
|
|
59
|
+
```liquid
|
|
60
|
+
{% section 'header_custom_elx.liquid' %}
|
|
61
|
+
{{ content_for_layout }}
|
|
62
|
+
{% section 'footer_custom_elx.liquid' %}
|
|
63
|
+
```
|
|
64
|
+
3. The tag `{{ content_for_layout }}` is crucial as it represents the content of the page, the pages (templates) will not be displayed without it
|
|
65
|
+
4. The custom section is MANDATORY, the admin uses it for third-party services, scripts and styles:
|
|
66
|
+
```liquid
|
|
67
|
+
<script>
|
|
68
|
+
{% setting "el-x.custom_js" %}
|
|
69
|
+
</script>
|
|
70
|
+
<style>
|
|
71
|
+
{% setting "el-x.custom_css" %}
|
|
72
|
+
</style>
|
|
73
|
+
{% setting "el-x.custom_html" %}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Sections
|
|
77
|
+
|
|
78
|
+
- The only allowed section names for now are:
|
|
79
|
+
- `header`
|
|
80
|
+
- `footer`
|
|
81
|
+
- `hero`
|
|
82
|
+
- `ad`
|
|
83
|
+
- `banner`
|
|
84
|
+
- `banner_1`
|
|
85
|
+
- `banner_2`
|
|
86
|
+
- `banner_3`
|
|
87
|
+
- `reviews`
|
|
88
|
+
- `menu`
|
|
89
|
+
- `menu_1`
|
|
90
|
+
- `menu_2`
|
|
91
|
+
- `menu_3`
|
|
92
|
+
- `category`
|
|
93
|
+
- `category_related`
|
|
94
|
+
- `categories`
|
|
95
|
+
- `category_element`
|
|
96
|
+
- `course`
|
|
97
|
+
- `course_related`
|
|
98
|
+
- `courses`
|
|
99
|
+
- `course_element`
|
|
100
|
+
- `testimonial`
|
|
101
|
+
- `about`
|
|
102
|
+
- `cta`
|
|
103
|
+
- `cta_1`
|
|
104
|
+
- `cta_2`
|
|
105
|
+
- `cta_3`
|
|
106
|
+
Note: The sections can be loaded using the `section` tag from any template, layout or section, using the syntax: `{% section '<SECTION_NAME>_custom_elx.liquid' %}`, example: `{% section 'header_custom_elx.liquid' %}`
|
|
107
|
+
|
|
108
|
+
### Templates
|
|
109
|
+
|
|
110
|
+
- The templates represent the pages of the website
|
|
111
|
+
- The `home_page.liquid` is the home page of the website
|
|
112
|
+
- The `course_page.liquid` is a single course page on the website
|
|
113
|
+
- The `courses_page.liquid` is the page that shows all courses on the website
|
|
114
|
+
- The `category_page.liquid` s a single category page on the website
|
|
115
|
+
- The `blog_page.liquid` is a single blog page on the website
|
|
116
|
+
- The `blogs_page.liquid` is the main blogs page of the website
|
|
117
|
+
- The `contact_page.liquid` is the contact page of the website
|
|
118
|
+
- The `about_page.liquid` is the about page of the website
|
|
119
|
+
|
|
120
|
+
### Available Liquid Variables
|
|
121
|
+
|
|
122
|
+
**Global Available vairables:**
|
|
123
|
+
Note: Available in All pages, and configurable from the website administration, recommended to use, so the user can change them from the admin, without the need to edit the code
|
|
124
|
+
- Path to the logo image:
|
|
125
|
+
+ Name: `logo`
|
|
126
|
+
+ Sample: `https://example.com/logo.png`
|
|
127
|
+
- Primary Color:
|
|
128
|
+
+ Name: `primary`
|
|
129
|
+
+ Sample: `#ff0000`
|
|
130
|
+
- Secondary Color:
|
|
131
|
+
+ Name: `secondary`
|
|
132
|
+
+ Sample: `#00ff00`
|
|
133
|
+
- Texts used in the footer
|
|
134
|
+
+ Name: `footerTexts`
|
|
135
|
+
+ Sample:
|
|
136
|
+
```json
|
|
137
|
+
{
|
|
138
|
+
"descreption": "...", // Footer description (yes it's misspelled as `descreption`, it will be fixed in the future)
|
|
139
|
+
"newsletter": "...", // Newsletter text
|
|
140
|
+
"copyright": "..." // Copyright text
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
- Links to be used in the footer
|
|
144
|
+
+ Name: `footerLinks`
|
|
145
|
+
+ Sample:
|
|
146
|
+
```json
|
|
147
|
+
[
|
|
148
|
+
{
|
|
149
|
+
"title": "...", // Link title
|
|
150
|
+
"link": "..." // Link url
|
|
151
|
+
},
|
|
152
|
+
... // The admin can add as many links as he wants
|
|
153
|
+
]
|
|
154
|
+
```
|
|
155
|
+
- Social media Urls
|
|
156
|
+
+ Name: `footerSocial`
|
|
157
|
+
+ Sample:
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"twitter": "...",
|
|
161
|
+
"facebook": "...",
|
|
162
|
+
"linkedIn": "..."
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
- About us texts:
|
|
166
|
+
+ Name: `aboutUsTexts`
|
|
167
|
+
+ Sample:
|
|
168
|
+
```json
|
|
169
|
+
{
|
|
170
|
+
"title": "...",
|
|
171
|
+
"descreption": "..."
|
|
172
|
+
"picture_one": {
|
|
173
|
+
"path": "...", // Image path
|
|
174
|
+
}
|
|
175
|
+
"picture_two": {
|
|
176
|
+
"path": "...", // Image path
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
```
|
|
180
|
+
- CTA texts:
|
|
181
|
+
+ Name: `ctaTexts`
|
|
182
|
+
+ Sample:
|
|
183
|
+
```json
|
|
184
|
+
{
|
|
185
|
+
"title": "...",
|
|
186
|
+
"button": "..."
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
- Apply now text:
|
|
190
|
+
+ Name: `applyNow`
|
|
191
|
+
+ Sample:
|
|
192
|
+
```json
|
|
193
|
+
[
|
|
194
|
+
{
|
|
195
|
+
"title": "...",
|
|
196
|
+
"descreption": "..."
|
|
197
|
+
"picture": {
|
|
198
|
+
"path": "...", // Image path
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
... // The admin can add as many applyNow as he wants
|
|
202
|
+
]
|
|
203
|
+
```
|
|
204
|
+
- Blog section text:
|
|
205
|
+
+ Name: `blogText`
|
|
206
|
+
+ Sample:
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"title": "...",
|
|
210
|
+
"descreption": "...",
|
|
211
|
+
"number_blogs": 5 // The limit of blogs to be displayed in the blog main page (Configurable from the admin)
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
- Pages top banner background:
|
|
215
|
+
+ Name: `pagesTopBannerBackground`
|
|
216
|
+
+ Sample:
|
|
217
|
+
```json
|
|
218
|
+
{
|
|
219
|
+
"picture": {
|
|
220
|
+
"path": "...", // Image path
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
```
|
|
224
|
+
- Promotion background:
|
|
225
|
+
+ Name: `promotionBackground`
|
|
226
|
+
+ Sample:
|
|
227
|
+
```json
|
|
228
|
+
{
|
|
229
|
+
"picture": {
|
|
230
|
+
"path": "...", // Image path
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
```
|
|
234
|
+
- Address:
|
|
235
|
+
+ Name: `adresse`
|
|
236
|
+
+ Sample: `"123 Main St, City"`
|
|
237
|
+
- Phone:
|
|
238
|
+
+ Name: `phone`
|
|
239
|
+
+ Sample: `"+1 234 567 890"`
|
|
240
|
+
- Email:
|
|
241
|
+
+ Name: `email`
|
|
242
|
+
+ Sample: `"contact@example.com"`
|
|
243
|
+
- Frame (embedded Maps iframe code):
|
|
244
|
+
+ Name: `frame`
|
|
245
|
+
+ Sample: `"<iframe>...</iframe>"`
|
|
246
|
+
- Icon for the browser tab:
|
|
247
|
+
+ Name: `icon`
|
|
248
|
+
+ Sample: `"https://example.com/icon.png"`
|
|
249
|
+
- All categories:
|
|
250
|
+
+ Name: `allCategories`
|
|
251
|
+
+ Sample:
|
|
252
|
+
```json
|
|
253
|
+
[
|
|
254
|
+
{ ... }, // Category object
|
|
255
|
+
...
|
|
256
|
+
]
|
|
257
|
+
```
|
|
258
|
+
- Website URL (The url of the website):
|
|
259
|
+
+ Name: `websiteUrl`
|
|
260
|
+
+ Sample: `"https://example.com"`
|
|
261
|
+
- Admin URL (The url of the website App, where users and admins can login):
|
|
262
|
+
+ Name: `adminUrl`
|
|
263
|
+
+ Sample: `"https://admin.example.com"`
|
|
264
|
+
+ Login url: `{{ adminUrl }}/public/el-x/login`
|
|
265
|
+
+ Register url: `{{ adminUrl }}/public/el-x/register`
|
|
266
|
+
- Website name:
|
|
267
|
+
+ Name: `websiteName`
|
|
268
|
+
+ Sample: `"My Website"`
|
|
269
|
+
- Navbar categories style:
|
|
270
|
+
+ Name: `navbarCategoriesStyle`
|
|
271
|
+
+ Sample: `true` or `false` (If true, the categories will be displayed in the navbar as a dropdown menu, parent [with parentId == null])
|
|
272
|
+
- Language:
|
|
273
|
+
+ Name: `lang`
|
|
274
|
+
+ Sample: `"en"`, `"ar"` or `"fr"`
|
|
275
|
+
- Current user:
|
|
276
|
+
+ Name: `user`
|
|
277
|
+
+ Sample:
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"id": 1,
|
|
281
|
+
"name": "John Doe",
|
|
282
|
+
"email": "john@example.com",
|
|
283
|
+
"image": {
|
|
284
|
+
"path": "...", // Image path
|
|
285
|
+
"thumbnail": "...", // Lower res, and truncated 200x200 variant of the image
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Home Page Available vairables:**
|
|
291
|
+
- Page template key:
|
|
292
|
+
+ Name: `template`
|
|
293
|
+
+ Value: key that includes the string `home_page`
|
|
294
|
+
- All categories:
|
|
295
|
+
+ Name: `categories`
|
|
296
|
+
+ Sample:
|
|
297
|
+
```json
|
|
298
|
+
[
|
|
299
|
+
{ // Category object
|
|
300
|
+
...
|
|
301
|
+
"courses": [ // Additional field per category
|
|
302
|
+
{ ... }, // Course object
|
|
303
|
+
...
|
|
304
|
+
]
|
|
305
|
+
},
|
|
306
|
+
...
|
|
307
|
+
]
|
|
308
|
+
```
|
|
309
|
+
- All courses:
|
|
310
|
+
+ Name: `allCourses`
|
|
311
|
+
+ Sample:
|
|
312
|
+
```json
|
|
313
|
+
[
|
|
314
|
+
{ ... }, // Course object
|
|
315
|
+
...
|
|
316
|
+
]
|
|
317
|
+
```
|
|
318
|
+
- All reviews:
|
|
319
|
+
+ Name: `allReviews`
|
|
320
|
+
+ Sample:
|
|
321
|
+
```json
|
|
322
|
+
[
|
|
323
|
+
{ ... }, // Review object
|
|
324
|
+
...
|
|
325
|
+
]
|
|
326
|
+
```
|
|
327
|
+
- List of all courses on promo (with `course.promo == true`):
|
|
328
|
+
+ Name: `coursePromo`
|
|
329
|
+
+ Sample:
|
|
330
|
+
```json
|
|
331
|
+
[
|
|
332
|
+
{ ... }, // Course object
|
|
333
|
+
...
|
|
334
|
+
]
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
**Course Page Available vairables:**
|
|
338
|
+
- Page template key:
|
|
339
|
+
+ Name: `template`
|
|
340
|
+
+ Value: key that includes the string `course_page`
|
|
341
|
+
- Course object:
|
|
342
|
+
+ Name: `course`
|
|
343
|
+
+ Sample:
|
|
344
|
+
```json
|
|
345
|
+
{ ... } // Course object
|
|
346
|
+
```
|
|
347
|
+
- Course Elements array:
|
|
348
|
+
+ Name: `chapterWithElements`
|
|
349
|
+
+ Sample:
|
|
350
|
+
```json
|
|
351
|
+
[
|
|
352
|
+
{ ... }, // Course Element object
|
|
353
|
+
...
|
|
354
|
+
]
|
|
355
|
+
```
|
|
356
|
+
+ **Important**: Never ever show the content of the course elements with `element.free == false` (The user must purchase the course to access the content), and always show the content of the course elements with `element.free == true && element.type == "video"` (The user need to watch the free video to decide if he wants to purchase the course or not)
|
|
357
|
+
- Course duration:
|
|
358
|
+
+ Name: `minutes`
|
|
359
|
+
+ Sample: `45` (number of minutes)
|
|
360
|
+
- Course duration in hours:
|
|
361
|
+
+ Name: `hours`
|
|
362
|
+
+ Sample: `2` (number of hours)
|
|
363
|
+
- Courses list from the same category:
|
|
364
|
+
+ Name: `courses`
|
|
365
|
+
+ Sample:
|
|
366
|
+
```json
|
|
367
|
+
[
|
|
368
|
+
{ ... }, // Course object
|
|
369
|
+
...
|
|
370
|
+
]
|
|
371
|
+
```
|
|
372
|
+
- If online payment is enabled (User can pay with credit card):
|
|
373
|
+
+ Name: `onlineMode`
|
|
374
|
+
+ Sample: `true` or `false`
|
|
375
|
+
- If offline payment is enabled (User can demand the course, and admin will contact the user):
|
|
376
|
+
+ Name: `offlineMode`
|
|
377
|
+
+ Sample: `true` or `false`
|
|
378
|
+
- If the course is purchased (User has already purchased the course):
|
|
379
|
+
+ Name: `isPurchased`
|
|
380
|
+
+ Sample: `true` or `false`
|
|
381
|
+
+ Course Url if purchased: `<ADMIN_URL>/p/el-x/course-player/<COURSE_ID>`
|
|
382
|
+
|
|
383
|
+
**Courses Page Available vairables:**
|
|
384
|
+
- Page template key:
|
|
385
|
+
+ Name: `template`
|
|
386
|
+
+ Value: key that includes the string `courses_page`
|
|
387
|
+
- Courses list (With pagination and search):
|
|
388
|
+
+ Name: `courses`
|
|
389
|
+
+ Sample:
|
|
390
|
+
```json
|
|
391
|
+
[
|
|
392
|
+
{ ... }, // Course object
|
|
393
|
+
...
|
|
394
|
+
]
|
|
395
|
+
```
|
|
396
|
+
- Keyword:
|
|
397
|
+
+ Name: `keyword`
|
|
398
|
+
+ Sample: `...` (search keyword)
|
|
399
|
+
- Total items:
|
|
400
|
+
+ Name: `totalItems`
|
|
401
|
+
+ Sample: `100` (number of courses)
|
|
402
|
+
- Current page:
|
|
403
|
+
+ Name: `currentPage`
|
|
404
|
+
+ Sample: `100` (number of courses)
|
|
405
|
+
- Page size:
|
|
406
|
+
+ Name: `pageSize`
|
|
407
|
+
+ Sample: `10` (number of courses per page)
|
|
408
|
+
- Total pages:
|
|
409
|
+
+ Name: `totalPages`
|
|
410
|
+
+ Sample: `10` (number of pages)
|
|
411
|
+
- Query Parameters:
|
|
412
|
+
+ `?keyword=...` => `keyword` variable
|
|
413
|
+
+ `?page=...` => `currentPage` variable
|
|
414
|
+
+ `?page_size=...` => `pageSize` variable
|
|
415
|
+
+ `?sort_by_tag=...` => handled by the server
|
|
416
|
+
+ `?sort_by_direction=...` => handled by the server
|
|
417
|
+
|
|
418
|
+
**Category Page Available vairables:**
|
|
419
|
+
- Page template key:
|
|
420
|
+
+ Name: `template`
|
|
421
|
+
+ Value: key that includes the string `categories_page`
|
|
422
|
+
- Courses list of the category:
|
|
423
|
+
+ Name: `courses`
|
|
424
|
+
+ Sample:
|
|
425
|
+
```json
|
|
426
|
+
[
|
|
427
|
+
{ ... }, // Course object
|
|
428
|
+
...
|
|
429
|
+
]
|
|
430
|
+
```
|
|
431
|
+
- Category:
|
|
432
|
+
+ Name: `category`
|
|
433
|
+
+ Sample:
|
|
434
|
+
```json
|
|
435
|
+
{ ... }, // Category object
|
|
436
|
+
```
|
|
437
|
+
|
|
438
|
+
**Blog Page Available vairables:**
|
|
439
|
+
- Page template key:
|
|
440
|
+
+ Name: `template`
|
|
441
|
+
+ Value: key that includes the string `blog_page`
|
|
442
|
+
- Page title:
|
|
443
|
+
+ Name: `page_title`
|
|
444
|
+
+ Sample: `...` (page title)
|
|
445
|
+
- Blog Post:
|
|
446
|
+
+ Name: `article`
|
|
447
|
+
+ Sample:
|
|
448
|
+
```json
|
|
449
|
+
{ ... }, // Article object
|
|
450
|
+
```
|
|
451
|
+
|
|
452
|
+
**Blogs Page Available vairables:**
|
|
453
|
+
- Page template key:
|
|
454
|
+
+ Name: `template`
|
|
455
|
+
+ Value: key that includes the string `blogs_page`
|
|
456
|
+
- Page title:
|
|
457
|
+
+ Name: `page_title`
|
|
458
|
+
+ Sample: `...` (page title)
|
|
459
|
+
- Blog Posts:
|
|
460
|
+
+ Name: `articles`
|
|
461
|
+
+ Sample:
|
|
462
|
+
```json
|
|
463
|
+
[
|
|
464
|
+
{ ... }, // Article object
|
|
465
|
+
...
|
|
466
|
+
]
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
**Contact Page Available vairables:**
|
|
470
|
+
- Page template key:
|
|
471
|
+
+ Name: `template`
|
|
472
|
+
+ Value: key that includes the string `contact_page`
|
|
473
|
+
|
|
474
|
+
**About Page Available vairables:**
|
|
475
|
+
- Page template key:
|
|
476
|
+
+ Name: `template`
|
|
477
|
+
+ Value: key that includes the string `about_page`
|
|
478
|
+
- All reviews:
|
|
479
|
+
+ Name: `allReviews`
|
|
480
|
+
+ Sample:
|
|
481
|
+
```json
|
|
482
|
+
[
|
|
483
|
+
{ ... }, // Review object
|
|
484
|
+
...
|
|
485
|
+
]
|
|
486
|
+
```
|
|
487
|
+
- List of all courses on promo (with `course.promo == true`):
|
|
488
|
+
+ Name: `coursePromo`
|
|
489
|
+
+ Sample:
|
|
490
|
+
```json
|
|
491
|
+
[
|
|
492
|
+
{ ... }, // Course object
|
|
493
|
+
...
|
|
494
|
+
]
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
**Objects Structure:**
|
|
498
|
+
- Course Object:
|
|
499
|
+
```json
|
|
500
|
+
{
|
|
501
|
+
"id": "...",
|
|
502
|
+
"name": "...",
|
|
503
|
+
"description": "...",
|
|
504
|
+
"promo": true, // If true, the course is on promo
|
|
505
|
+
"price": "99.99", // "0" if free
|
|
506
|
+
"barredPrice": "99.99", // The price before the promo
|
|
507
|
+
"logo": {
|
|
508
|
+
"path": "..."
|
|
509
|
+
},
|
|
510
|
+
"elements": [ // Use {{ elements.size }} to get length in liquid
|
|
511
|
+
"...", // Course Element ID
|
|
512
|
+
...
|
|
513
|
+
]
|
|
514
|
+
}
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
- Course Element Object:
|
|
518
|
+
```json
|
|
519
|
+
{
|
|
520
|
+
"freePreview": false, // If true, and type is "video", the `element.content` will be available
|
|
521
|
+
"type": "...", // Types: quiz, youtube, video, pdf, iframe, video-iframe
|
|
522
|
+
"title": "...", // The title of the element
|
|
523
|
+
"time": {
|
|
524
|
+
"minutes": 3,
|
|
525
|
+
"seconds": 20
|
|
526
|
+
},
|
|
527
|
+
"content": { // Only available if type is "video"
|
|
528
|
+
"path": "..." // The path of the video
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
- Category Object:
|
|
533
|
+
```json
|
|
534
|
+
{
|
|
535
|
+
"parentId": null, // Or the id of the parent category
|
|
536
|
+
"id": "...",
|
|
537
|
+
"logo": {
|
|
538
|
+
"path": "...", // Image path
|
|
539
|
+
"thumbnail": "...", // Lower res, and truncated 200x200 variant of the image
|
|
540
|
+
},
|
|
541
|
+
"name": "..."
|
|
542
|
+
}
|
|
543
|
+
```
|
|
544
|
+
|
|
545
|
+
- Article Object:
|
|
546
|
+
```json
|
|
547
|
+
{
|
|
548
|
+
"image": {
|
|
549
|
+
"path": "..." // Image path
|
|
550
|
+
},
|
|
551
|
+
"excerpt": "...",
|
|
552
|
+
"parsedBody": "...", // HTML content
|
|
553
|
+
"title": "...",
|
|
554
|
+
"formatted_created_at": "...", // Formatted date
|
|
555
|
+
"formatted_updated_at": "...", // Formatted date
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
- Review Object:
|
|
559
|
+
```json
|
|
560
|
+
{
|
|
561
|
+
"userName": "...",
|
|
562
|
+
"reviewContent": "...",
|
|
563
|
+
"userImage": {
|
|
564
|
+
"path": "..."
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
```
|