diff --git a/.eslintrc.json b/.eslintrc.json
index c9233886e..ba4aa8fea 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -10,14 +10,13 @@
"plugin:react/recommended"
],
"globals": {
- "Promise": true,
"requirejs": true,
- "Backbone": true,
- "$": true,
- "ENUM": true,
- "Modernizr": true,
- "_": true,
- "Handlebars": true,
+ "Backbone": "off",
+ "$": "off",
+ "ENUM": "off",
+ "Modernizr": "off",
+ "_": "off",
+ "Handlebars": "off",
"Atomics": "readonly",
"SharedArrayBuffer": "readonly"
},
@@ -31,6 +30,29 @@
"requirejs"
],
"rules": {
+ "no-restricted-globals": [
+ "error",
+ {
+ "name": "ENUM",
+ "message": "Use: import ENUM from 'enum';"
+ },
+ {
+ "name": "Backbone",
+ "message": "Use: import Backbone from 'backbone';"
+ },
+ {
+ "name": "Handlebars",
+ "message": "Use: import Handlebars from 'handlebars';"
+ },
+ {
+ "name": "_",
+ "message": "Use: import _ from 'underscore';"
+ },
+ {
+ "name": "$",
+ "message": "Use: import $ from 'jquery';"
+ }
+ ],
"indent": ["error", 2, { "SwitchCase": 1 }],
"array-bracket-spacing": "off",
"semi": ["error", "always"],
diff --git a/.gitignore b/.gitignore
index b4b2ec84a..f865d77f9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,8 +5,13 @@ src/extensions
src/less
src/less/adapt.less
src/plugins.js
-build
bower_components
-node_modules
.DS_Store
-.idea
\ No newline at end of file
+.idea
+build/*
+!build/course
+!build/course/*
+build/course/*/language_data_manifest.js
+node_modules
+src/node_modules/
+src/package-lock.json
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 3221fc71c..000000000
--- a/.gitmodules
+++ /dev/null
@@ -1,5 +0,0 @@
-[submodule "src/core"]
- path = src/core
- branch = master
- url = https://github.com/adaptlearning/adapt-contrib-core
- installBranch = v6.25.5
diff --git a/Gruntfile.js b/Gruntfile.js
index 00c2760fd..6efcf8201 100755
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,8 +1,8 @@
var path = require('path');
module.exports = function(grunt) {
- var helpers = require('./grunt/helpers')(grunt);
-
+ const helpers = require('./grunt/helpers')(grunt);
+ grunt.option('helpers', helpers);
require('time-grunt')(grunt);
require('load-grunt-config')(grunt, {
data: helpers.generateConfigData(),
@@ -14,8 +14,6 @@ module.exports = function(grunt) {
}
}
});
-
- grunt.config('helpers', helpers);
grunt.registerTask('default', ['help']);
};
diff --git a/src/course/config.json b/build/course/config.json
similarity index 100%
rename from src/course/config.json
rename to build/course/config.json
diff --git a/src/course/en/articles.json b/build/course/en/articles.json
old mode 100755
new mode 100644
similarity index 100%
rename from src/course/en/articles.json
rename to build/course/en/articles.json
diff --git a/src/course/en/blocks.json b/build/course/en/blocks.json
similarity index 100%
rename from src/course/en/blocks.json
rename to build/course/en/blocks.json
diff --git a/src/course/en/components.json b/build/course/en/components.json
old mode 100755
new mode 100644
similarity index 97%
rename from src/course/en/components.json
rename to build/course/en/components.json
index 8fd0fed5c..4a1221d92
--- a/src/course/en/components.json
+++ b/build/course/en/components.json
@@ -1,1100 +1,1100 @@
-[
- {
- "_id": "c-05",
- "_parentId": "b-05",
- "_type": "component",
- "_component": "text",
- "_classes": "",
- "_layout": "left",
- "title": "Introduction",
- "displayTitle": "",
- "body": "Adapt allows you to combine Text and Graphic components on the scrolling page to create rich and varied learning experiences. In addition, a wide range of interactive components are also available to help encourage deeper engagement with the material.
These presentation components can be structured in any way to help you create learning experiences that meet the needs of your learners.
In addition, if your course is being launched from an LMS or LRS, you can personalise the course content by having the learner's name dynamically displayed in the body, instruction or feedback text.",
- "instruction": "{{_globals._learnerInfo.firstname}}, scroll down to see what presentation components are available as part of the core bundle.",
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-10",
- "_parentId": "b-05",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-10",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "large": "course/en/images/single-width.png",
- "small": "course/en/images/full-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-15",
- "_parentId": "b-10",
- "_type": "component",
- "_component": "text",
- "_classes": "",
- "_layout": "left",
- "title": "Text",
- "displayTitle": "Text",
- "body": "The simple Text component can often be the best choice for imparting information, particularly when used in conjunction with complementary graphics. In this example we’ve used the Blank component to the right to create a window through to the block background.
Remember, content doesn’t always warrant an interaction so less can often be really be more. Instead, look to intersperse more interactive components with text and graphics where they add the maximum value.
Component can either be single or spanned.",
- "instruction": "",
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-20",
- "_parentId": "b-10",
- "_type": "component",
- "_component": "blank",
- "_classes": "",
- "_layout": "right",
- "title": "c-20",
- "_isOptional": true
- },
- {
- "_id": "c-25",
- "_parentId": "b-15",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "full",
- "title": "Graphic",
- "displayTitle": "Graphic",
- "body": "This is the Graphic component body. You can introduce your graphic or you may just want to let the graphics do the talking.
Component can either be single or spanned.",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/full-width.png",
- "alt": "",
- "attribution": ""
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-30",
- "_parentId": "b-20",
- "_type": "component",
- "_component": "narrative",
- "_classes": "",
- "_layout": "full",
- "_comment": "setCompletionOn = inview | allItems",
- "_setCompletionOn": "allItems",
- "_hasNavigationInTextArea": false,
- "title": "Narrative",
- "displayTitle": "Narrative",
- "body": "The Narrative component lets you scroll through a series of images each with some accompanying text.
This component is always spanned.",
- "instruction": "Select the next and back arrows to find out more.",
- "mobileInstruction": "Select the plus icon followed by the next arrow to find out more.",
- "_items": [
- {
- "title": "Narrative item 1",
- "body": "Narratives are particularly good for showing dialogue between two or more characters, with each step of the conversation being accompanied by an image. This photo story approach can be used to provide context for the learning to follow, to illustrate real-world application of the learning or to show the impact on people when the learning hasn’t been applied correctly.",
- "_graphic": {
- "src": "course/en/images/narrative.png",
- "alt": "alt text"
- },
- "strapline": "Narrative item 1"
- },
- {
- "title": "Narrative item 2",
- "body": "It can also be used to present case studies, where the different displays are used to set the scene, show the key events and then the outcome.",
- "_graphic": {
- "src": "course/en/images/narrative.png",
- "alt": "alt text"
- },
- "strapline": "Narrative item 2"
- },
- {
- "title": "Narrative item 3",
- "body": "This component can also be useful when you want to illustrate the constituent steps that make up a larger process.",
- "_graphic": {
- "src": "course/en/images/narrative.png",
- "alt": "alt text"
- },
- "strapline": "Narrative item 3"
- }
- ],
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-35",
- "_parentId": "b-25",
- "_type": "component",
- "_component": "hotgraphic",
- "_classes": "",
- "_layout": "full",
- "_comment": "setCompletionOn = inview | allItems",
- "_setCompletionOn": "allItems",
- "_canCycleThroughPagination": false,
- "_hidePagination": false,
- "_isNarrativeOnMobile": true,
- "_useNumberedPins": false,
- "_useGraphicsAsPins": false,
- "title": "Hot Graphic",
- "displayTitle": "Hot Graphic",
- "body": "You can add interactivity to an image by using the Hot Graphic. This component allows you to position icons over an image. When an icon is selected, content associated with its corresponding location is displayed in a window over the image. This component will fall back to a Narrative when viewed on mobile.
This component is always spanned.",
- "instruction": "Select the icons to find out more.",
- "mobileInstruction": "Select the plus icon followed by the next arrow to find out more.",
- "_graphic": {
- "src": "course/en/images/hotgraphic.png",
- "alt": "alt text"
- },
- "_items": [
- {
- "title": "Hot Graphic item 1",
- "body": "This is display text associated with item 1.",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "_classes": ""
- },
- "strapline": "Hot Graphic strapline 1",
- "_classes": "",
- "_top": 42,
- "_left": 8.5
- },
- {
- "title": "Hot Graphic item 2",
- "body": "This is display text associated with item 2.",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "_classes": ""
- },
- "strapline": "Hot Graphic strapline 2",
- "_classes": "",
- "_top": 62,
- "_left": 26.5
- },
- {
- "title": "Hot Graphic item 3",
- "body": "This is display text associated with item 3.",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "_classes": ""
- },
- "strapline": "Hot Graphic strapline 3",
- "_classes": "",
- "_top": 62,
- "_left": 49
- }
- ],
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-40",
- "_parentId": "b-30",
- "_type": "component",
- "_component": "media",
- "_classes": "",
- "_layout": "full",
- "_comment": "_setCompletionOn = inview | play | ended",
- "_setCompletionOn": "play",
- "_useClosedCaptions": true,
- "_allowFullScreen": true,
- "_startLanguage": "en",
- "title": "Media",
- "displayTitle": "Media",
- "body": "Sometimes we need to make use of rich media to really drive home a point, bring a complex subject to life or simply use audio to literally do the talking. This is when the Media component comes to the fore.
Component can be either single or spanned.",
- "instruction": "Select the play button to start the video.",
- "_media": {
- "mp4": "course/en/video/c-40.mp4",
- "poster": "course/en/video/c-40.jpg",
- "cc": [
- {
- "srclang": "en",
- "src": "course/en/video/c-40.vtt"
- }
- ]
- },
- "_playerOptions": {
- "alwaysShowControls": true,
- "toggleCaptionsButtonWhenOnlyOne": true,
- "iPadUseNativeControls": true,
- "iPhoneUseNativeControls": true,
- "AndroidUseNativeControls": true
- },
- "_transcript": {
- "_inlineTranscript": true,
- "_externalTranscript": false,
- "inlineTranscriptButton": "Transcript",
- "inlineTranscriptCloseButton": "Close Transcript",
- "inlineTranscriptBody": "Responsive design means you can create a course once but view it on a wide range of browsers, devices or operating systems. This is achieved by using fluid layouts to ensure the presentation of content is tailored to suit the size of the screen. This means you can start a course on your laptop and finish it later on your mobile or tablet. We call it Adapt",
- "transcriptLinkButton": "Transcript",
- "transcriptLink": ""
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-45",
- "_parentId": "b-35",
- "_type": "component",
- "_component": "accordion",
- "_classes": "",
- "_layout": "full",
- "title": "Accordion",
- "displayTitle": "Accordion",
- "body": "You can use the Accordion component to present learners with a series of headings which, once selected, expand to reveal associated text. Select each of the headings below to find out more about how accordions can be used.
These components can be either single or spanned. If spanned they can also contain a graphic.",
- "instruction": "Select the headings to find out more.",
- "_items": [
- {
- "title": "Lists",
- "body": "This is display text 1 and we’re using it to discuss lists.
If you need to present a list that can stand alone as a piece of content, but which can also be explored in more detail, accordions are a great choice.",
- "_classes": "align-image-left",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": ""
- }
- },
- {
- "title": "Steps in a process",
- "body": "This is display text 2 and now we’re discussing how accordions can be great at presenting a process.
The headings can be used to present the high-level stages in the process, which once selected, provide a more extensive explanation of what happens at that specific point.",
- "_classes": "align-image-right",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": ""
- }
- },
- {
- "title": "Recaps",
- "body": "This is display text 3 and we’re now talking about using accordions to provide the learner with a summary.
Each accordion item corresponds to a key piece of learning presented on the page. When this item is selected a short summary of the learning point is provided.",
- "_graphic": {
- "src": "course/en/images/accordion-full.png",
- "alt": ""
- }
- }
- ],
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-60",
- "_parentId": "b-40",
- "_type": "component",
- "_component": "mcq",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": true,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 1,
- "title": "Multiple Choice Question",
- "displayTitle": "Multiple Choice Question",
- "body": "In what year was the first recorded instance of a large scale assessment that consists solely of multiple choice questions?",
- "instruction": "Choose one option and select Submit.",
- "ariaQuestion": "",
- "_items": [
- {
- "text": "1917",
- "_shouldBeSelected": true
- },
- {
- "text": "1888",
- "_shouldBeSelected": false
- },
- {
- "text": "1953",
- "_shouldBeSelected": false
- },
- {
- "text": "1977",
- "_shouldBeSelected": false
- }
- ],
- "_feedback": {
- "title": "Feedback",
- "correct": "Correct feedback text.
That’s correct. The first large assessment to consist solely of the multiple choice question type was the Army Alpha test, used from 1917 to evaluate U.S. military recruits in the First World War.
Source: Wikipedia
Component facts: Multiple Choice Questions (or MCQs) are a tried and tested method for presenting learners with a simple text-based question. Component is either single or spanned.",
- "_incorrect": {
- "final": "Incorrect feedback text.
That’s not right. The first large assessment to consist solely of the multiple choice question type was the Army Alpha test, used from 1917 to evaluate U.S. military recruits in the First World War.
Source: Wikipedia
Component facts: Multiple Choice Questions (or MCQs) are a tried and tested method for presenting learners with a simple text-based question. Component is either single or spanned."
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-62",
- "_parentId": "b-40",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-62",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-65",
- "_parentId": "b-45",
- "_type": "component",
- "_component": "gmcq",
- "_classes": "",
- "_layout": "full",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": true,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 1,
- "title": "Graphical Multiple Choice Question",
- "displayTitle": "Graphical Multiple Choice Question",
- "body": "The Graphical Multiple Choice Question is an alternative to the standard MCQ, and allows you to ask a question and present the options as graphics (with accompanying captions). This component is always spanned.",
- "instruction": "Choose option 2 and select Submit.",
- "ariaQuestion": "",
- "_columns": 3,
- "_items": [
- {
- "text": "Option 1",
- "_shouldBeSelected": false,
- "_graphic": {
- "large": "course/en/images/gmcq.png",
- "small": "course/en/images/gmcq.png",
- "alt": "alt text"
- }
- },
- {
- "text": "Option 2",
- "_shouldBeSelected": true,
- "_graphic": {
- "large": "course/en/images/gmcq.png",
- "small": "course/en/images/gmcq.png",
- "alt": "alt text"
- }
- },
- {
- "text": "Option 3",
- "_shouldBeSelected": false,
- "_graphic": {
- "large": "course/en/images/gmcq.png",
- "small": "course/en/images/gmcq.png",
- "alt": "alt text"
- }
- }
- ],
- "_feedback": {
- "title": "Feedback",
- "correct": "Correct feedback text.
That’s correct. Option 2 is what we were looking for!",
- "_incorrect": {
- "final": "Incorrect feedback text.
Sorry, that’s not right. In fact Option 2 is what we were looking for (the clue was in the instruction text!)."
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-70",
- "_parentId": "b-50",
- "_type": "component",
- "_component": "textinput",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": true,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_allowsAnyCase": true,
- "_allowsPunctuation": true,
- "title": "Text Input",
- "displayTitle": "Text Input",
- "body": "Can you name one of the three companies that established Adapt as a community-led open source project?",
- "instruction": "Input your answer and select Submit.",
- "ariaQuestion": "",
- "_answers": [
- ["City & Guilds Kineo", "Kineo"],
- ["Learning Pool"],
- ["Sponge"]
- ],
- "_items": [
- {
- "prefix": "",
- "placeholder": "Enter answer here",
- "suffix": ""
- }
- ],
- "_feedback": {
- "title": "Feedback",
- "correct": "Correct answer feedback.
That’s correct. The Adapt open source project was established by Kineo, Learning Pool and Sponge. At the time of writing, there were a total of nine official collaborators.
Component facts: Text input components let learners enter their own answers, which is great for questions that require a bit more flexibility, like those with answers that could be written as both full words and numbers.",
- "_partlyCorrect": {
- "final": "Partially correct answer feedback.
That’s partially correct. The Adapt open source project was established by Kineo, Learning Pool and Sponge. At the time of writing, there were a total of nine official collaborators.
Component facts: Text input components let learners enter their own answers, which is great for questions that require a bit more flexibility, like those with answers that could be written as both full words and numbers."
- },
- "_incorrect": {
- "final": "Incorrect answer feedback.
Sorry, that’s not right. The Adapt open source project was established by Kineo, Learning Pool and Sponge. At the time of writing, there were a total of nine official collaborators.
Component facts: Text input components let learners enter their own answers, which is great for questions that require a bit more flexibility, like those with answers that could be written as both full words and numbers."
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-72",
- "_parentId": "b-50",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-72",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-75",
- "_parentId": "b-55",
- "_type": "component",
- "_component": "matching",
- "_classes": "",
- "_layout": "full",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": true,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "title": "Matching",
- "displayTitle": "Matching",
- "body": "Can you identify some of the key facts and figures associated with the Adapt Open Source project?",
- "instruction": "Choose an option from each dropdown list and select Submit.",
- "ariaQuestion": "",
- "placeholder": "Select an option",
- "_items": [
- {
- "text": "The Adapt Open Source project was formed in:",
- "_options": [
- {
- "text": "2008",
- "_isCorrect": false
- },
- {
- "text": "2010",
- "_isCorrect": false
- },
- {
- "text": "2013",
- "_isCorrect": true
- },
- {
- "text": "2015",
- "_isCorrect": false
- }
- ]
- },
- {
- "text": "Adapt adheres to level 'AA' of the WCAG version 2, but when were these published?",
- "_options": [
- {
- "text": "2008",
- "_isCorrect": true
- },
- {
- "text": "2010",
- "_isCorrect": false
- },
- {
- "text": "2013",
- "_isCorrect": false
- },
- {
- "text": "2015",
- "_isCorrect": false
- }
- ]
- },
- {
- "text": "Which of these languages would benefit from using the recent addition of Right To Left (RTL) language support within Adapt?",
- "_options": [
- {
- "text": "Portuguese",
- "_isCorrect": false
- },
- {
- "text": "Hebrew",
- "_isCorrect": true
- },
- {
- "text": "Finnish",
- "_isCorrect": false
- },
- {
- "text": "Esperanto",
- "_isCorrect": false
- }
- ]
- }
- ],
- "_feedback": {
- "title": "Feedback",
- "correct": "Correct answer feedback.
Yes, that’s right. Adapt was established as an Open Source project in 2013. The Web Accessibility Initiative (WAI) published the WCAG 2.0 guidelines in 2008 and Hebrew is the only language of the four shown which reads right to left.
Component facts: The Matching component lets you match a series of questions or statements with the corresponding options from the dropdown box. Component is either single or spanned.",
- "_partlyCorrect": {
- "final": "Partially correct answer feedback.
That’s partially correct. Adapt was established as an Open Source project in 2013. The Web Accessibility Initiative (WAI) published the WCAG 2.0 guidelines in 2008 and Hebrew is the only language of the four shown which reads right to left.
Component facts: The Matching component lets you match a series of questions or statements with the corresponding options from the dropdown box. Component is either single or spanned."
- },
- "_incorrect": {
- "final": "Incorrect answer feedback.
Sorry, that’s not right. Adapt was established as an Open Source project in 2013. The Web Accessibility Initiative (WAI) published the WCAG 2.0 guidelines in 2008 and Hebrew is the only language of the four shown which reads right to left.
Component facts: The Matching component lets you match a series of questions or statements with the corresponding options from the dropdown box. Component is either single or spanned."
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-80",
- "_parentId": "b-60",
- "_type": "component",
- "_component": "slider",
- "_classes": "",
- "_layout": "full",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": true,
- "_shouldDisplayAttempts": false,
- "_showNumber": true,
- "_showScaleIndicator": true,
- "_scaleStart": 1,
- "_scaleEnd": 10,
- "title": "Slider",
- "displayTitle": "Slider",
- "body": "Working memory is thought to be responsible for our ability to temporarily hold and manipulate information, but according to a paper by Miller (1956) what ‘magic number’ describes its capacity?",
- "instruction": "Drag the slider to make your choice and select Submit.",
- "ariaQuestion": "",
- "labelStart": "",
- "labelEnd": "",
- "_correctAnswer": "",
- "_correctRange": {
- "_bottom": 5,
- "_top": 9
- },
- "_feedback": {
- "title": "Feedback",
- "correct": "Correct answer feedback.
Yes, that’s right. According to Miller’s paper 7 +/- 2 chunks of information was the limited capacity of working memory. Various theories from the field of cognitive psychology such as Miller’s magical number, chunking, the forgetting curve and spaced repetition have all influenced learning theory over the last 50 years or so.
Component facts: The correct answer for a Slider component can be an exact number or a range. In this instance we have set the correct answer to a range of 5-9 due to the correct answer being seven plus or minus two. Component is either single or spanned.",
- "_incorrect": {
- "final": "Incorrect answer feedback.
Sorry, that’s not right. According to Miller’s paper 7 +/- 2 chunks of information was the limited capacity of working memory. Various theories from the field of cognitive psychology such as Miller’s magical number, chunking, the forgetting curve and spaced repetition have all influenced learning theory over the last 50 years or so.
Component facts: The correct answer for a Slider component can be an exact number or a range. In this instance we have set the correct answer to a range of 5-9 due to the correct answer being seven plus or minus two. Component is either single or spanned."
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": true
- }
- },
- {
- "_id": "c-90",
- "_parentId": "b-65",
- "_type": "component",
- "_component": "mcq",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": false,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 4,
- "title": "To scroll or not to scroll?",
- "displayTitle": "To scroll or not to scroll?",
- "body": "Can you identify a few of the benefits that a vertical scrolling approach offers over the traditional back and next approach?",
- "instruction": "Choose one or more options and select Submit.",
- "ariaQuestion": "",
- "_items": [
- {
- "text": "Scrolling is common place on the web and therefore familiar to the learner",
- "_shouldBeSelected": true
- },
- {
- "text": "Scrolling reduces the need for unnecessary navigation as pages can be as long (or as short) as they need to be",
- "_shouldBeSelected": true
- },
- {
- "text": "Scrolling helps ensure designs are more mobile friendly",
- "_shouldBeSelected": true
- },
- {
- "text": "Scrolling means you no longer need to first gain learners attention",
- "_shouldBeSelected": false
- }
- ],
- "_feedback": {
- "title": "",
- "correct": "",
- "_partlyCorrect": {
- "final": ""
- },
- "_incorrect": {
- "final": ""
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-95",
- "_parentId": "b-65",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-95",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-100",
- "_parentId": "b-75",
- "_type": "component",
- "_component": "mcq",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": false,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 5,
- "title": "Do you know your plugins?",
- "displayTitle": "Do you know your plugins?",
- "body": "Which of the following are types of Adapt plugins?",
- "instruction": "Choose one or more options and select Submit.",
- "ariaQuestion": "",
- "_items": [
- {
- "text": "Component",
- "_shouldBeSelected": true
- },
- {
- "text": "Extension",
- "_shouldBeSelected": true
- },
- {
- "text": "Schema",
- "_shouldBeSelected": false
- },
- {
- "text": "Block",
- "_shouldBeSelected": false
- },
- {
- "text": "App Wrapper",
- "_shouldBeSelected": false
- }
- ],
- "_feedback": {
- "title": "",
- "correct": "",
- "_partlyCorrect": {
- "final": ""
- },
- "_incorrect": {
- "final": ""
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-105",
- "_parentId": "b-75",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-105",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-110",
- "_parentId": "b-80",
- "_type": "component",
- "_component": "mcq",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": false,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 1,
- "title": "It’s as simple as A, B, C",
- "displayTitle": "It’s as simple as A, B, C",
- "body": "Can you identify the missing word in the following sequence?
Article / ..... / Component.",
- "instruction": "Choose an option and select Submit.",
- "ariaQuestion": "",
- "_items": [
- {
- "text": "Block",
- "_shouldBeSelected": true
- },
- {
- "text": "Bridge",
- "_shouldBeSelected": false
- },
- {
- "text": "Brace",
- "_shouldBeSelected": false
- },
- {
- "text": "Base",
- "_shouldBeSelected": false
- },
- {
- "text": "Bearing",
- "_shouldBeSelected": false
- }
- ],
- "_feedback": {
- "title": "",
- "correct": "",
- "_incorrect": {
- "final": ""
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-115",
- "_parentId": "b-80",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-115",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-120",
- "_parentId": "b-85",
- "_type": "component",
- "_component": "mcq",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": false,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 4,
- "title": "Content Objects",
- "displayTitle": "Content Objects",
- "body": "What would you be referring to when describing a Content Object within Adapt?",
- "instruction": "Choose one or more options and select Submit.",
- "ariaQuestion": "",
- "_items": [
- {
- "text": "Page",
- "_shouldBeSelected": true
- },
- {
- "text": "Menu",
- "_shouldBeSelected": true
- },
- {
- "text": "Component",
- "_shouldBeSelected": false
- },
- {
- "text": "Course configuration options",
- "_shouldBeSelected": false
- }
- ],
- "_feedback": {
- "title": "",
- "correct": "",
- "_partlyCorrect": {
- "final": ""
- },
- "_incorrect": {
- "final": ""
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-125",
- "_parentId": "b-85",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-95",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-130",
- "_parentId": "b-90",
- "_type": "component",
- "_component": "mcq",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": false,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 1,
- "title": "Adding titles to icons",
- "displayTitle": "Adding titles to icons",
- "body": "What JSON file would you need to edit in order to add a title to an icon in the navigation bar?",
- "instruction": "Choose an option and select Submit.",
- "ariaQuestion": "",
- "_items": [
- {
- "text": "course.json",
- "_shouldBeSelected": true
- },
- {
- "text": "config.json",
- "_shouldBeSelected": false
- },
- {
- "text": "contentObjects.json",
- "_shouldBeSelected": false
- },
- {
- "text": "components.json",
- "_shouldBeSelected": false
- }
- ],
- "_feedback": {
- "title": "",
- "correct": "",
- "_incorrect": {
- "final": ""
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-135",
- "_parentId": "b-90",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-95",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-140",
- "_parentId": "b-95",
- "_type": "component",
- "_component": "mcq",
- "_classes": "",
- "_layout": "left",
- "_attempts": 1,
- "_questionWeight": 1,
- "_canShowModelAnswer": false,
- "_shouldDisplayAttempts": false,
- "_isRandom": true,
- "_selectable": 4,
- "title": "Control the scroll",
- "displayTitle": "Control the scroll",
- "body": "The Trickle extension makes it possible to control how the learner scrolls down the page, but what elements of a page can it be applied to?",
- "instruction": "Choose an option and select Submit.",
- "ariaQuestion": "",
- "_items": [
- {
- "text": "Article",
- "_shouldBeSelected": true
- },
- {
- "text": "Block",
- "_shouldBeSelected": true
- },
- {
- "text": "Component",
- "_shouldBeSelected": false
- },
- {
- "text": "Other extensions",
- "_shouldBeSelected": false
- }
- ],
- "_feedback": {
- "title": "",
- "correct": "",
- "_partlyCorrect": {
- "final": ""
- },
- "_incorrect": {
- "final": ""
- }
- },
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-145",
- "_parentId": "b-95",
- "_type": "component",
- "_component": "graphic",
- "_classes": "",
- "_layout": "right",
- "_isOptional": true,
- "title": "c-95",
- "displayTitle": "",
- "body": "",
- "instruction": "",
- "_graphic": {
- "src": "course/en/images/single-width.png",
- "alt": "",
- "attribution": ""
- }
- },
- {
- "_id": "c-147",
- "_parentId": "b-100",
- "_type": "component",
- "_component": "text",
- "_classes": "",
- "_layout": "full",
- "title": "Scoring bands",
- "displayTitle": "",
- "body": "The Assessment Results component allows you to specify the assessment pass mark, and provide tailored feedback to as many scoring bands as you wish.
In this short assessment we set the pass mark at 75% or above and provided four scoring bands, each of which has some accompanying feedback. The four scoring bands were:
- 0-49% (fail)
- 50-74% (fail)
- 75-99% (pass)
- 100% (perfect score)
So, how did you do? Let’s find out.",
- "_canReset": true,
- "_isResetOnRevisit": "hard",
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- },
- {
- "_id": "c-150",
- "_parentId": "b-105",
- "_type": "component",
- "_component": "assessmentResults",
- "_classes": "",
- "_layout": "full",
- "_isVisibleBeforeCompletion": false,
- "_assessmentId": "a-15",
- "title": "Results",
- "displayTitle": "Results",
- "body": "",
- "instruction": "",
- "_retry": {
- "button": "Retry Assessment",
- "_comment": "use {{attempts}}, {{attemptsSpent}} and {{attemptsLeft}} to display attempts",
- "feedback": ""
- },
- "_comment": "use {{score}}, {{maxScore}} and {{scoreAsPercent}} to display score and percentage",
- "_completionBody": "{{{feedback}}}",
- "_bands": [
- {
- "_score": 0,
- "feedback": "You didn’t pass, achieving a score of {{scoreAsPercent}}%.
You didn’t do so well in the Adapt assessment demo, but don’t worry. There’s lots of information about the Adapt basics on our site. We’d also suggest you pay a visit to the forum or chat room and ask our ever-helpful community to answer any questions that you might have.",
- "_allowRetry": true
- },
- {
- "_score": 50,
- "feedback": "You didn’t pass, achieving a score of {{scoreAsPercent}}%.
You already know some of the Adapt basics but please explore our site for more information. Also pay a visit to the forum or chat room and ask our ever-helpful community to answer any questions that you might have.",
- "_allowRetry": true
- },
- {
- "_score": 75,
- "feedback": "You passed, achieving a score of {{scoreAsPercent}}%.
You’re clearly already pretty knowledgeable about Adapt so, if you haven’t already, please consider sharing your expertise on the Adapt community forums.",
- "_allowRetry": true
- },
- {
- "_score": 100,
- "feedback": "You passed, achieving a score of {{scoreAsPercent}}%.
You’re clearly already a black belt in Adapt! If you haven’t already, please consider sharing your expertise on the Adapt community forums.",
- "_allowRetry": false
- }
- ],
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_isCompletionIndicatorEnabled": false
- }
- }
-]
+[
+ {
+ "_id": "c-05",
+ "_parentId": "b-05",
+ "_type": "component",
+ "_component": "text",
+ "_classes": "",
+ "_layout": "left",
+ "title": "Introduction",
+ "displayTitle": "",
+ "body": "Adapt allows you to combine Text and Graphic components on the scrolling page to create rich and varied learning experiences. In addition, a wide range of interactive components are also available to help encourage deeper engagement with the material.
These presentation components can be structured in any way to help you create learning experiences that meet the needs of your learners.
In addition, if your course is being launched from an LMS or LRS, you can personalise the course content by having the learner's name dynamically displayed in the body, instruction or feedback text.",
+ "instruction": "{{_globals._learnerInfo.firstname}}, scroll down to see what presentation components are available as part of the core bundle.",
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-10",
+ "_parentId": "b-05",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-10",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "large": "course/en/images/single-width.png",
+ "small": "course/en/images/full-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-15",
+ "_parentId": "b-10",
+ "_type": "component",
+ "_component": "text",
+ "_classes": "",
+ "_layout": "left",
+ "title": "Text",
+ "displayTitle": "Text",
+ "body": "The simple Text component can often be the best choice for imparting information, particularly when used in conjunction with complementary graphics. In this example we’ve used the Blank component to the right to create a window through to the block background.
Remember, content doesn’t always warrant an interaction so less can often be really be more. Instead, look to intersperse more interactive components with text and graphics where they add the maximum value.
Component can either be single or spanned.",
+ "instruction": "",
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-20",
+ "_parentId": "b-10",
+ "_type": "component",
+ "_component": "blank",
+ "_classes": "",
+ "_layout": "right",
+ "title": "c-20",
+ "_isOptional": true
+ },
+ {
+ "_id": "c-25",
+ "_parentId": "b-15",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "full",
+ "title": "Graphic",
+ "displayTitle": "Graphic",
+ "body": "This is the Graphic component body. You can introduce your graphic or you may just want to let the graphics do the talking.
Component can either be single or spanned.",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/full-width.png",
+ "alt": "",
+ "attribution": ""
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-30",
+ "_parentId": "b-20",
+ "_type": "component",
+ "_component": "narrative",
+ "_classes": "",
+ "_layout": "full",
+ "_comment": "setCompletionOn = inview | allItems",
+ "_setCompletionOn": "allItems",
+ "_hasNavigationInTextArea": false,
+ "title": "Narrative",
+ "displayTitle": "Narrative",
+ "body": "The Narrative component lets you scroll through a series of images each with some accompanying text.
This component is always spanned.",
+ "instruction": "Select the next and back arrows to find out more.",
+ "mobileInstruction": "Select the plus icon followed by the next arrow to find out more.",
+ "_items": [
+ {
+ "title": "Narrative item 1",
+ "body": "Narratives are particularly good for showing dialogue between two or more characters, with each step of the conversation being accompanied by an image. This photo story approach can be used to provide context for the learning to follow, to illustrate real-world application of the learning or to show the impact on people when the learning hasn’t been applied correctly.",
+ "_graphic": {
+ "src": "course/en/images/narrative.png",
+ "alt": "alt text"
+ },
+ "strapline": "Narrative item 1"
+ },
+ {
+ "title": "Narrative item 2",
+ "body": "It can also be used to present case studies, where the different displays are used to set the scene, show the key events and then the outcome.",
+ "_graphic": {
+ "src": "course/en/images/narrative.png",
+ "alt": "alt text"
+ },
+ "strapline": "Narrative item 2"
+ },
+ {
+ "title": "Narrative item 3",
+ "body": "This component can also be useful when you want to illustrate the constituent steps that make up a larger process.",
+ "_graphic": {
+ "src": "course/en/images/narrative.png",
+ "alt": "alt text"
+ },
+ "strapline": "Narrative item 3"
+ }
+ ],
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-35",
+ "_parentId": "b-25",
+ "_type": "component",
+ "_component": "hotgraphic",
+ "_classes": "",
+ "_layout": "full",
+ "_comment": "setCompletionOn = inview | allItems",
+ "_setCompletionOn": "allItems",
+ "_canCycleThroughPagination": false,
+ "_hidePagination": false,
+ "_isNarrativeOnMobile": true,
+ "_useNumberedPins": false,
+ "_useGraphicsAsPins": false,
+ "title": "Hot Graphic",
+ "displayTitle": "Hot Graphic",
+ "body": "You can add interactivity to an image by using the Hot Graphic. This component allows you to position icons over an image. When an icon is selected, content associated with its corresponding location is displayed in a window over the image. This component will fall back to a Narrative when viewed on mobile.
This component is always spanned.",
+ "instruction": "Select the icons to find out more.",
+ "mobileInstruction": "Select the plus icon followed by the next arrow to find out more.",
+ "_graphic": {
+ "src": "course/en/images/hotgraphic.png",
+ "alt": "alt text"
+ },
+ "_items": [
+ {
+ "title": "Hot Graphic item 1",
+ "body": "This is display text associated with item 1.",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "_classes": ""
+ },
+ "strapline": "Hot Graphic strapline 1",
+ "_classes": "",
+ "_top": 42,
+ "_left": 8.5
+ },
+ {
+ "title": "Hot Graphic item 2",
+ "body": "This is display text associated with item 2.",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "_classes": ""
+ },
+ "strapline": "Hot Graphic strapline 2",
+ "_classes": "",
+ "_top": 62,
+ "_left": 26.5
+ },
+ {
+ "title": "Hot Graphic item 3",
+ "body": "This is display text associated with item 3.",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "_classes": ""
+ },
+ "strapline": "Hot Graphic strapline 3",
+ "_classes": "",
+ "_top": 62,
+ "_left": 49
+ }
+ ],
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-40",
+ "_parentId": "b-30",
+ "_type": "component",
+ "_component": "media",
+ "_classes": "",
+ "_layout": "full",
+ "_comment": "_setCompletionOn = inview | play | ended",
+ "_setCompletionOn": "play",
+ "_useClosedCaptions": true,
+ "_allowFullScreen": true,
+ "_startLanguage": "en",
+ "title": "Media",
+ "displayTitle": "Media",
+ "body": "Sometimes we need to make use of rich media to really drive home a point, bring a complex subject to life or simply use audio to literally do the talking. This is when the Media component comes to the fore.
Component can be either single or spanned.",
+ "instruction": "Select the play button to start the video.",
+ "_media": {
+ "mp4": "course/en/video/c-40.mp4",
+ "poster": "course/en/video/c-40.jpg",
+ "cc": [
+ {
+ "srclang": "en",
+ "src": "course/en/video/c-40.vtt"
+ }
+ ]
+ },
+ "_playerOptions": {
+ "alwaysShowControls": true,
+ "toggleCaptionsButtonWhenOnlyOne": true,
+ "iPadUseNativeControls": true,
+ "iPhoneUseNativeControls": true,
+ "AndroidUseNativeControls": true
+ },
+ "_transcript": {
+ "_inlineTranscript": true,
+ "_externalTranscript": false,
+ "inlineTranscriptButton": "Transcript",
+ "inlineTranscriptCloseButton": "Close Transcript",
+ "inlineTranscriptBody": "Responsive design means you can create a course once but view it on a wide range of browsers, devices or operating systems. This is achieved by using fluid layouts to ensure the presentation of content is tailored to suit the size of the screen. This means you can start a course on your laptop and finish it later on your mobile or tablet. We call it Adapt",
+ "transcriptLinkButton": "Transcript",
+ "transcriptLink": ""
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-45",
+ "_parentId": "b-35",
+ "_type": "component",
+ "_component": "accordion",
+ "_classes": "",
+ "_layout": "full",
+ "title": "Accordion",
+ "displayTitle": "Accordion",
+ "body": "You can use the Accordion component to present learners with a series of headings which, once selected, expand to reveal associated text. Select each of the headings below to find out more about how accordions can be used.
These components can be either single or spanned. If spanned they can also contain a graphic.",
+ "instruction": "Select the headings to find out more.",
+ "_items": [
+ {
+ "title": "Lists",
+ "body": "This is display text 1 and we’re using it to discuss lists.
If you need to present a list that can stand alone as a piece of content, but which can also be explored in more detail, accordions are a great choice.",
+ "_classes": "align-image-left",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": ""
+ }
+ },
+ {
+ "title": "Steps in a process",
+ "body": "This is display text 2 and now we’re discussing how accordions can be great at presenting a process.
The headings can be used to present the high-level stages in the process, which once selected, provide a more extensive explanation of what happens at that specific point.",
+ "_classes": "align-image-right",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": ""
+ }
+ },
+ {
+ "title": "Recaps",
+ "body": "This is display text 3 and we’re now talking about using accordions to provide the learner with a summary.
Each accordion item corresponds to a key piece of learning presented on the page. When this item is selected a short summary of the learning point is provided.",
+ "_graphic": {
+ "src": "course/en/images/accordion-full.png",
+ "alt": ""
+ }
+ }
+ ],
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-60",
+ "_parentId": "b-40",
+ "_type": "component",
+ "_component": "mcq",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": true,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 1,
+ "title": "Multiple Choice Question",
+ "displayTitle": "Multiple Choice Question",
+ "body": "In what year was the first recorded instance of a large scale assessment that consists solely of multiple choice questions?",
+ "instruction": "Choose one option and select Submit.",
+ "ariaQuestion": "",
+ "_items": [
+ {
+ "text": "1917",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "1888",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "1953",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "1977",
+ "_shouldBeSelected": false
+ }
+ ],
+ "_feedback": {
+ "title": "Feedback",
+ "correct": "Correct feedback text.
That’s correct. The first large assessment to consist solely of the multiple choice question type was the Army Alpha test, used from 1917 to evaluate U.S. military recruits in the First World War.
Source: Wikipedia
Component facts: Multiple Choice Questions (or MCQs) are a tried and tested method for presenting learners with a simple text-based question. Component is either single or spanned.",
+ "_incorrect": {
+ "final": "Incorrect feedback text.
That’s not right. The first large assessment to consist solely of the multiple choice question type was the Army Alpha test, used from 1917 to evaluate U.S. military recruits in the First World War.
Source: Wikipedia
Component facts: Multiple Choice Questions (or MCQs) are a tried and tested method for presenting learners with a simple text-based question. Component is either single or spanned."
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-62",
+ "_parentId": "b-40",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-62",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-65",
+ "_parentId": "b-45",
+ "_type": "component",
+ "_component": "gmcq",
+ "_classes": "",
+ "_layout": "full",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": true,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 1,
+ "title": "Graphical Multiple Choice Question",
+ "displayTitle": "Graphical Multiple Choice Question",
+ "body": "The Graphical Multiple Choice Question is an alternative to the standard MCQ, and allows you to ask a question and present the options as graphics (with accompanying captions). This component is always spanned.",
+ "instruction": "Choose option 2 and select Submit.",
+ "ariaQuestion": "",
+ "_columns": 3,
+ "_items": [
+ {
+ "text": "Option 1",
+ "_shouldBeSelected": false,
+ "_graphic": {
+ "large": "course/en/images/gmcq.png",
+ "small": "course/en/images/gmcq.png",
+ "alt": "alt text"
+ }
+ },
+ {
+ "text": "Option 2",
+ "_shouldBeSelected": true,
+ "_graphic": {
+ "large": "course/en/images/gmcq.png",
+ "small": "course/en/images/gmcq.png",
+ "alt": "alt text"
+ }
+ },
+ {
+ "text": "Option 3",
+ "_shouldBeSelected": false,
+ "_graphic": {
+ "large": "course/en/images/gmcq.png",
+ "small": "course/en/images/gmcq.png",
+ "alt": "alt text"
+ }
+ }
+ ],
+ "_feedback": {
+ "title": "Feedback",
+ "correct": "Correct feedback text.
That’s correct. Option 2 is what we were looking for!",
+ "_incorrect": {
+ "final": "Incorrect feedback text.
Sorry, that’s not right. In fact Option 2 is what we were looking for (the clue was in the instruction text!)."
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-70",
+ "_parentId": "b-50",
+ "_type": "component",
+ "_component": "textinput",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": true,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_allowsAnyCase": true,
+ "_allowsPunctuation": true,
+ "title": "Text Input",
+ "displayTitle": "Text Input",
+ "body": "Can you name one of the three companies that established Adapt as a community-led open source project?",
+ "instruction": "Input your answer and select Submit.",
+ "ariaQuestion": "",
+ "_answers": [
+ ["City & Guilds Kineo", "Kineo"],
+ ["Learning Pool"],
+ ["Sponge"]
+ ],
+ "_items": [
+ {
+ "prefix": "",
+ "placeholder": "Enter answer here",
+ "suffix": ""
+ }
+ ],
+ "_feedback": {
+ "title": "Feedback",
+ "correct": "Correct answer feedback.
That’s correct. The Adapt open source project was established by Kineo, Learning Pool and Sponge. At the time of writing, there were a total of nine official collaborators.
Component facts: Text input components let learners enter their own answers, which is great for questions that require a bit more flexibility, like those with answers that could be written as both full words and numbers.",
+ "_partlyCorrect": {
+ "final": "Partially correct answer feedback.
That’s partially correct. The Adapt open source project was established by Kineo, Learning Pool and Sponge. At the time of writing, there were a total of nine official collaborators.
Component facts: Text input components let learners enter their own answers, which is great for questions that require a bit more flexibility, like those with answers that could be written as both full words and numbers."
+ },
+ "_incorrect": {
+ "final": "Incorrect answer feedback.
Sorry, that’s not right. The Adapt open source project was established by Kineo, Learning Pool and Sponge. At the time of writing, there were a total of nine official collaborators.
Component facts: Text input components let learners enter their own answers, which is great for questions that require a bit more flexibility, like those with answers that could be written as both full words and numbers."
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-72",
+ "_parentId": "b-50",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-72",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-75",
+ "_parentId": "b-55",
+ "_type": "component",
+ "_component": "matching",
+ "_classes": "",
+ "_layout": "full",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": true,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "title": "Matching",
+ "displayTitle": "Matching",
+ "body": "Can you identify some of the key facts and figures associated with the Adapt Open Source project?",
+ "instruction": "Choose an option from each dropdown list and select Submit.",
+ "ariaQuestion": "",
+ "placeholder": "Select an option",
+ "_items": [
+ {
+ "text": "The Adapt Open Source project was formed in:",
+ "_options": [
+ {
+ "text": "2008",
+ "_isCorrect": false
+ },
+ {
+ "text": "2010",
+ "_isCorrect": false
+ },
+ {
+ "text": "2013",
+ "_isCorrect": true
+ },
+ {
+ "text": "2015",
+ "_isCorrect": false
+ }
+ ]
+ },
+ {
+ "text": "Adapt adheres to level 'AA' of the WCAG version 2, but when were these published?",
+ "_options": [
+ {
+ "text": "2008",
+ "_isCorrect": true
+ },
+ {
+ "text": "2010",
+ "_isCorrect": false
+ },
+ {
+ "text": "2013",
+ "_isCorrect": false
+ },
+ {
+ "text": "2015",
+ "_isCorrect": false
+ }
+ ]
+ },
+ {
+ "text": "Which of these languages would benefit from using the recent addition of Right To Left (RTL) language support within Adapt?",
+ "_options": [
+ {
+ "text": "Portuguese",
+ "_isCorrect": false
+ },
+ {
+ "text": "Hebrew",
+ "_isCorrect": true
+ },
+ {
+ "text": "Finnish",
+ "_isCorrect": false
+ },
+ {
+ "text": "Esperanto",
+ "_isCorrect": false
+ }
+ ]
+ }
+ ],
+ "_feedback": {
+ "title": "Feedback",
+ "correct": "Correct answer feedback.
Yes, that’s right. Adapt was established as an Open Source project in 2013. The Web Accessibility Initiative (WAI) published the WCAG 2.0 guidelines in 2008 and Hebrew is the only language of the four shown which reads right to left.
Component facts: The Matching component lets you match a series of questions or statements with the corresponding options from the dropdown box. Component is either single or spanned.",
+ "_partlyCorrect": {
+ "final": "Partially correct answer feedback.
That’s partially correct. Adapt was established as an Open Source project in 2013. The Web Accessibility Initiative (WAI) published the WCAG 2.0 guidelines in 2008 and Hebrew is the only language of the four shown which reads right to left.
Component facts: The Matching component lets you match a series of questions or statements with the corresponding options from the dropdown box. Component is either single or spanned."
+ },
+ "_incorrect": {
+ "final": "Incorrect answer feedback.
Sorry, that’s not right. Adapt was established as an Open Source project in 2013. The Web Accessibility Initiative (WAI) published the WCAG 2.0 guidelines in 2008 and Hebrew is the only language of the four shown which reads right to left.
Component facts: The Matching component lets you match a series of questions or statements with the corresponding options from the dropdown box. Component is either single or spanned."
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-80",
+ "_parentId": "b-60",
+ "_type": "component",
+ "_component": "slider",
+ "_classes": "",
+ "_layout": "full",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": true,
+ "_shouldDisplayAttempts": false,
+ "_showNumber": true,
+ "_showScaleIndicator": true,
+ "_scaleStart": 1,
+ "_scaleEnd": 10,
+ "title": "Slider",
+ "displayTitle": "Slider",
+ "body": "Working memory is thought to be responsible for our ability to temporarily hold and manipulate information, but according to a paper by Miller (1956) what ‘magic number’ describes its capacity?",
+ "instruction": "Drag the slider to make your choice and select Submit.",
+ "ariaQuestion": "",
+ "labelStart": "",
+ "labelEnd": "",
+ "_correctAnswer": "",
+ "_correctRange": {
+ "_bottom": 5,
+ "_top": 9
+ },
+ "_feedback": {
+ "title": "Feedback",
+ "correct": "Correct answer feedback.
Yes, that’s right. According to Miller’s paper 7 +/- 2 chunks of information was the limited capacity of working memory. Various theories from the field of cognitive psychology such as Miller’s magical number, chunking, the forgetting curve and spaced repetition have all influenced learning theory over the last 50 years or so.
Component facts: The correct answer for a Slider component can be an exact number or a range. In this instance we have set the correct answer to a range of 5-9 due to the correct answer being seven plus or minus two. Component is either single or spanned.",
+ "_incorrect": {
+ "final": "Incorrect answer feedback.
Sorry, that’s not right. According to Miller’s paper 7 +/- 2 chunks of information was the limited capacity of working memory. Various theories from the field of cognitive psychology such as Miller’s magical number, chunking, the forgetting curve and spaced repetition have all influenced learning theory over the last 50 years or so.
Component facts: The correct answer for a Slider component can be an exact number or a range. In this instance we have set the correct answer to a range of 5-9 due to the correct answer being seven plus or minus two. Component is either single or spanned."
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": true
+ }
+ },
+ {
+ "_id": "c-90",
+ "_parentId": "b-65",
+ "_type": "component",
+ "_component": "mcq",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": false,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 4,
+ "title": "To scroll or not to scroll?",
+ "displayTitle": "To scroll or not to scroll?",
+ "body": "Can you identify a few of the benefits that a vertical scrolling approach offers over the traditional back and next approach?",
+ "instruction": "Choose one or more options and select Submit.",
+ "ariaQuestion": "",
+ "_items": [
+ {
+ "text": "Scrolling is common place on the web and therefore familiar to the learner",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Scrolling reduces the need for unnecessary navigation as pages can be as long (or as short) as they need to be",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Scrolling helps ensure designs are more mobile friendly",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Scrolling means you no longer need to first gain learners attention",
+ "_shouldBeSelected": false
+ }
+ ],
+ "_feedback": {
+ "title": "",
+ "correct": "",
+ "_partlyCorrect": {
+ "final": ""
+ },
+ "_incorrect": {
+ "final": ""
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-95",
+ "_parentId": "b-65",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-95",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-100",
+ "_parentId": "b-75",
+ "_type": "component",
+ "_component": "mcq",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": false,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 5,
+ "title": "Do you know your plugins?",
+ "displayTitle": "Do you know your plugins?",
+ "body": "Which of the following are types of Adapt plugins?",
+ "instruction": "Choose one or more options and select Submit.",
+ "ariaQuestion": "",
+ "_items": [
+ {
+ "text": "Component",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Extension",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Schema",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "Block",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "App Wrapper",
+ "_shouldBeSelected": false
+ }
+ ],
+ "_feedback": {
+ "title": "",
+ "correct": "",
+ "_partlyCorrect": {
+ "final": ""
+ },
+ "_incorrect": {
+ "final": ""
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-105",
+ "_parentId": "b-75",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-105",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-110",
+ "_parentId": "b-80",
+ "_type": "component",
+ "_component": "mcq",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": false,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 1,
+ "title": "It’s as simple as A, B, C",
+ "displayTitle": "It’s as simple as A, B, C",
+ "body": "Can you identify the missing word in the following sequence?
Article / ..... / Component.",
+ "instruction": "Choose an option and select Submit.",
+ "ariaQuestion": "",
+ "_items": [
+ {
+ "text": "Block",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Bridge",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "Brace",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "Base",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "Bearing",
+ "_shouldBeSelected": false
+ }
+ ],
+ "_feedback": {
+ "title": "",
+ "correct": "",
+ "_incorrect": {
+ "final": ""
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-115",
+ "_parentId": "b-80",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-115",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-120",
+ "_parentId": "b-85",
+ "_type": "component",
+ "_component": "mcq",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": false,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 4,
+ "title": "Content Objects",
+ "displayTitle": "Content Objects",
+ "body": "What would you be referring to when describing a Content Object within Adapt?",
+ "instruction": "Choose one or more options and select Submit.",
+ "ariaQuestion": "",
+ "_items": [
+ {
+ "text": "Page",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Menu",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Component",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "Course configuration options",
+ "_shouldBeSelected": false
+ }
+ ],
+ "_feedback": {
+ "title": "",
+ "correct": "",
+ "_partlyCorrect": {
+ "final": ""
+ },
+ "_incorrect": {
+ "final": ""
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-125",
+ "_parentId": "b-85",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-95",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-130",
+ "_parentId": "b-90",
+ "_type": "component",
+ "_component": "mcq",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": false,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 1,
+ "title": "Adding titles to icons",
+ "displayTitle": "Adding titles to icons",
+ "body": "What JSON file would you need to edit in order to add a title to an icon in the navigation bar?",
+ "instruction": "Choose an option and select Submit.",
+ "ariaQuestion": "",
+ "_items": [
+ {
+ "text": "course.json",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "config.json",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "contentObjects.json",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "components.json",
+ "_shouldBeSelected": false
+ }
+ ],
+ "_feedback": {
+ "title": "",
+ "correct": "",
+ "_incorrect": {
+ "final": ""
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-135",
+ "_parentId": "b-90",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-95",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-140",
+ "_parentId": "b-95",
+ "_type": "component",
+ "_component": "mcq",
+ "_classes": "",
+ "_layout": "left",
+ "_attempts": 1,
+ "_questionWeight": 1,
+ "_canShowModelAnswer": false,
+ "_shouldDisplayAttempts": false,
+ "_isRandom": true,
+ "_selectable": 4,
+ "title": "Control the scroll",
+ "displayTitle": "Control the scroll",
+ "body": "The Trickle extension makes it possible to control how the learner scrolls down the page, but what elements of a page can it be applied to?",
+ "instruction": "Choose an option and select Submit.",
+ "ariaQuestion": "",
+ "_items": [
+ {
+ "text": "Article",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Block",
+ "_shouldBeSelected": true
+ },
+ {
+ "text": "Component",
+ "_shouldBeSelected": false
+ },
+ {
+ "text": "Other extensions",
+ "_shouldBeSelected": false
+ }
+ ],
+ "_feedback": {
+ "title": "",
+ "correct": "",
+ "_partlyCorrect": {
+ "final": ""
+ },
+ "_incorrect": {
+ "final": ""
+ }
+ },
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-145",
+ "_parentId": "b-95",
+ "_type": "component",
+ "_component": "graphic",
+ "_classes": "",
+ "_layout": "right",
+ "_isOptional": true,
+ "title": "c-95",
+ "displayTitle": "",
+ "body": "",
+ "instruction": "",
+ "_graphic": {
+ "src": "course/en/images/single-width.png",
+ "alt": "",
+ "attribution": ""
+ }
+ },
+ {
+ "_id": "c-147",
+ "_parentId": "b-100",
+ "_type": "component",
+ "_component": "text",
+ "_classes": "",
+ "_layout": "full",
+ "title": "Scoring bands",
+ "displayTitle": "",
+ "body": "The Assessment Results component allows you to specify the assessment pass mark, and provide tailored feedback to as many scoring bands as you wish.
In this short assessment we set the pass mark at 75% or above and provided four scoring bands, each of which has some accompanying feedback. The four scoring bands were:
- 0-49% (fail)
- 50-74% (fail)
- 75-99% (pass)
- 100% (perfect score)
So, how did you do? Let’s find out.",
+ "_canReset": true,
+ "_isResetOnRevisit": "hard",
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ },
+ {
+ "_id": "c-150",
+ "_parentId": "b-105",
+ "_type": "component",
+ "_component": "assessmentResults",
+ "_classes": "",
+ "_layout": "full",
+ "_isVisibleBeforeCompletion": false,
+ "_assessmentId": "a-15",
+ "title": "Results",
+ "displayTitle": "Results",
+ "body": "",
+ "instruction": "",
+ "_retry": {
+ "button": "Retry Assessment",
+ "_comment": "use {{attempts}}, {{attemptsSpent}} and {{attemptsLeft}} to display attempts",
+ "feedback": ""
+ },
+ "_comment": "use {{score}}, {{maxScore}} and {{scoreAsPercent}} to display score and percentage",
+ "_completionBody": "{{{feedback}}}",
+ "_bands": [
+ {
+ "_score": 0,
+ "feedback": "You didn’t pass, achieving a score of {{scoreAsPercent}}%.
You didn’t do so well in the Adapt assessment demo, but don’t worry. There’s lots of information about the Adapt basics on our site. We’d also suggest you pay a visit to the forum or chat room and ask our ever-helpful community to answer any questions that you might have.",
+ "_allowRetry": true
+ },
+ {
+ "_score": 50,
+ "feedback": "You didn’t pass, achieving a score of {{scoreAsPercent}}%.
You already know some of the Adapt basics but please explore our site for more information. Also pay a visit to the forum or chat room and ask our ever-helpful community to answer any questions that you might have.",
+ "_allowRetry": true
+ },
+ {
+ "_score": 75,
+ "feedback": "You passed, achieving a score of {{scoreAsPercent}}%.
You’re clearly already pretty knowledgeable about Adapt so, if you haven’t already, please consider sharing your expertise on the Adapt community forums.",
+ "_allowRetry": true
+ },
+ {
+ "_score": 100,
+ "feedback": "You passed, achieving a score of {{scoreAsPercent}}%.
You’re clearly already a black belt in Adapt! If you haven’t already, please consider sharing your expertise on the Adapt community forums.",
+ "_allowRetry": false
+ }
+ ],
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_isCompletionIndicatorEnabled": false
+ }
+ }
+]
diff --git a/src/course/en/contentObjects.json b/build/course/en/contentObjects.json
old mode 100755
new mode 100644
similarity index 100%
rename from src/course/en/contentObjects.json
rename to build/course/en/contentObjects.json
diff --git a/build/course/en/course.json b/build/course/en/course.json
new file mode 100644
index 000000000..49fdb7b12
--- /dev/null
+++ b/build/course/en/course.json
@@ -0,0 +1,275 @@
+{
+ "_id": "course",
+ "_type": "course",
+ "_classes": "",
+ "_htmlClasses": "",
+ "title": "Adapt v5",
+ "displayTitle": "Adapt Version 5",
+ "description": "A sample course demonstrating the capabilities of the Adapt Framework",
+ "body": "Welcome to the demonstration build for version 5 of the Adapt framework.",
+ "instruction": "",
+ "_buttons": {
+ "_submit": {
+ "buttonText": "Submit",
+ "ariaLabel": "Submit"
+ },
+ "_reset": {
+ "buttonText": "Reset",
+ "ariaLabel": "Reset"
+ },
+ "_showCorrectAnswer": {
+ "buttonText": "Show correct answer",
+ "ariaLabel": "Show correct answer"
+ },
+ "_hideCorrectAnswer": {
+ "buttonText": "Show your answer",
+ "ariaLabel": "Show your answer"
+ },
+ "_showFeedback": {
+ "buttonText": "Show feedback",
+ "ariaLabel": "Show feedback"
+ },
+ "remainingAttemptsText": "attempts remaining",
+ "remainingAttemptText": "final attempt",
+ "disabledAriaLabel": "This button is disabled at the moment"
+ },
+ "_vanilla": {
+ "_favIcon": {
+ "_src": "course/en/images/cropped-nav_logo_gold-192x192.png"
+ }
+ },
+ "_globals": {
+ "_learnerInfo": {
+ "id": "student.name@example.org",
+ "name": "Name, Student",
+ "firstname": "Student",
+ "lastname": "Name"
+ },
+ "_accessibility": {
+ "skipNavigationText": "Skip navigation",
+ "_ariaLabels": {
+ "answeredIncorrectly": "You answered incorrectly",
+ "answeredCorrectly": "You answered correctly",
+ "selectedAnswer": "selected",
+ "unselectedAnswer": "not selected",
+ "skipNavigation": "Skip Navigation",
+ "previous": "Back",
+ "navigationDrawer": "Open course resources.",
+ "close": "Close",
+ "closeDrawer": "Close drawer",
+ "closeResources": "Close resources",
+ "drawer": "Top of side drawer",
+ "closePopup": "Close popup",
+ "next": "Next",
+ "done": "Done",
+ "complete": "Completed",
+ "incomplete": "Incomplete",
+ "incorrect": "Incorrect",
+ "correct": "Correct",
+ "locked": "Locked",
+ "visited": "Visited"
+ }
+ },
+ "_extensions": {
+ "_drawer": {
+ "_navOrder": 100
+ },
+ "_navigation": {
+ "_skipButton": {
+ "_navOrder": -100
+ },
+ "_backButton": {
+ "_navOrder": 0
+ },
+ "_spacers": [
+ {
+ "_navOrder": 0
+ }
+ ]
+ },
+ "_languagePicker": {
+ "navigationBarLabel": "Select course language",
+ "languageSelector": "Language selector"
+ },
+ "_pageLevelProgress": {
+ "pageLevelProgress": "Page sections",
+ "pageLevelProgressIndicatorBar": "Page progress. Use this to listen to the list of regions in this topic and whether they're completed. You can jump directly to any that are incomplete or which sound particularly interesting. {{percentageComplete}}%",
+ "pageLevelProgressMenuBar": "Page completion {{percentageComplete}}%",
+ "pageLevelProgressEnd": "You have reached the end of the list of page sections.",
+ "optionalContent": "Optional content",
+ "_navOrder": 90
+ },
+ "_resources": {
+ "resources": "Additional resources."
+ },
+ "_trickle": {
+ "incompleteContent": "There is incomplete content above. You must complete this before you can proceed through the course."
+ },
+ "_tutor": {
+ "hideFeedback": "Hide feedback"
+ }
+ },
+ "_components": {
+ "_accordion": {
+ "ariaRegion": "List of expandable sections. Select each button to expand the content."
+ },
+ "_assessmentResults": {
+ "ariaRegion": "Assessment results.",
+ "retryText": "Retry"
+ },
+ "_gmcq": {
+ "ariaRegion": "Multiple choice question",
+ "ariaCorrectAnswer": "The correct answer is {{{correctAnswer}}}",
+ "ariaCorrectAnswers": "The correct answers are {{{correctAnswer}}}",
+ "ariaUserAnswer": "The answer you chose was {{{userAnswer}}}",
+ "ariaUserAnswers": "The answers you chose were {{{userAnswer}}}"
+ },
+ "_graphic": {
+ "ariaRegion": "",
+ "scrollAriaLabel": "Use the scrollbar to pan the image left and right. {{#if _graphic.alt}}{{_graphic.alt}}{{/if}}"
+ },
+ "_hotgraphic": {
+ "ariaRegion": "Image with selectable areas. Select each button to show more information.",
+ "item": "Item {{{itemNumber}}} of {{{totalItems}}}",
+ "previous": "{{#if title}}Back to {{{title}}} (item {{itemNumber}} of {{totalItems}}){{else}}{{_globals._accessibility._ariaLabels.previous}}{{/if}}",
+ "next": "{{#if title}}Forward to {{{title}}} (item {{itemNumber}} of {{totalItems}}){{else}}{{_globals._accessibility._ariaLabels.next}}{{/if}}",
+ "popupPagination": "{{itemNumber}} / {{totalItems}}"
+ },
+ "_matching": {
+ "ariaRegion": "Matching. Select from lists and then submit.",
+ "ariaCorrectAnswer": "The correct answer for {{{itemText}}} is {{{correctAnswer}}}",
+ "ariaUserAnswer": "The answer you chose for {{{itemText}}} was {{{userAnswer}}}"
+ },
+ "_mcq": {
+ "ariaRegion": "Multiple choice question",
+ "ariaCorrectAnswer": "The correct answer is {{{correctAnswer}}}",
+ "ariaCorrectAnswers": "The correct answers are {{{correctAnswer}}}",
+ "ariaUserAnswer": "The answer you chose was {{{userAnswer}}}",
+ "ariaUserAnswers": "The answers you chose were {{{userAnswer}}}"
+ },
+ "_media": {
+ "ariaRegion": "Media player{{#any _transcript._inlineTranscript _transcript._externalTranscript}} and transcript{{/any}}.",
+ "skipToTranscript": "Skip to transcript",
+ "playText": "Play",
+ "pauseText": "Pause",
+ "stopText": "Stop",
+ "audioPlayerText": "Audio Player",
+ "videoPlayerText": "Video Player",
+ "tracksText": "Captions/Subtitles",
+ "timeSliderText": "Time Slider",
+ "muteText": "Mute Toggle",
+ "unmuteStatusText": "Unmute",
+ "muteStatusText": "Mute",
+ "volumeSliderText": "Volume Slider",
+ "fullscreenText": "Fullscreen",
+ "goFullscreenText": "Go Fullscreen",
+ "turnOffFullscreenText": "Turn off Fullscreen",
+ "noneText": "None",
+ "skipBackText": "Skip back %1 seconds",
+ "allyVolumeControlText": "Use Up/Down Arrow keys to increase or decrease volume.",
+ "progessHelpText": "Use Left/Right Arrow keys to advance one second, Up/Down arrows to advance ten seconds."
+ },
+ "_narrative": {
+ "ariaRegion": "Slide show. Select the next button to progress.",
+ "previous": "{{#if title}}Back to {{{title}}} (item {{itemNumber}} of {{totalItems}}){{else}}{{_globals._accessibility._ariaLabels.previous}}{{/if}}",
+ "next": "{{#if title}}Forward to {{{title}}} (item {{itemNumber}} of {{totalItems}}){{else}}{{_globals._accessibility._ariaLabels.next}}{{/if}}"
+ },
+ "_slider": {
+ "ariaRegion": "Slider. Respond to the question by selecting a value on the scale and then submit.",
+ "ariaCorrectAnswer": "The correct answer is {{{correctAnswer}}}",
+ "ariaCorrectAnswerRange": "The correct answer is any value from {{{bottom}}} to {{{top}}}",
+ "ariaUserAnswer": "The answer you chose was {{{userAnswer}}}",
+ "labelStart": "Start of the scale",
+ "labelEnd": "End of the scale"
+ },
+ "_text": {
+ "ariaRegion": ""
+ },
+ "_textinput": {
+ "ariaRegion": "Text input. Type your answer and then submit."
+ }
+ },
+ "_menu": {
+ "_boxMenu": {
+ "durationLabel": "Duration:"
+ }
+ }
+ },
+ "_latestTrackingId": 19,
+ "_pageLevelProgress": {
+ "_isEnabled": true,
+ "_showPageCompletion": false,
+ "_isCompletionIndicatorEnabled": false,
+ "_isShownInNavigationBar": true
+ },
+ "_resources": {
+ "_isEnabled": true,
+ "title": "Resources",
+ "description": "View resources for this course by clicking here.",
+ "_filterButtons": {
+ "all": "All",
+ "document": "Document",
+ "media": "Media",
+ "link": "Link"
+ },
+ "_filterAria": {
+ "allAria": "View all resources",
+ "documentAria": "View document resources",
+ "mediaAria": "View media resources",
+ "linkAria": "View resource links"
+ },
+ "_resourcesItems": [
+ {
+ "_type": "document",
+ "title": "Vanilla Theme Swatch",
+ "description": "See the swatch for the vanilla theme by clicking here.",
+ "_link": "course/en/images/vanilla-swatch.jpg"
+ },
+ {
+ "_type": "media",
+ "title": "Adapt Learning YouTube Channel",
+ "description": "Fancy catching up on some Adapt material? Click here.",
+ "_link": "https://www.youtube.com/channel/UCW8SlSFuCc--B66Gf9fAEcQ"
+ },
+ {
+ "_type": "link",
+ "title": "Adapt Project Site",
+ "description": "View the project's web site by clicking here.",
+ "_link": "https://www.adaptlearning.org/"
+ },
+ {
+ "_type": "link",
+ "title": "Framework chat",
+ "description": "Join the framework chat room by clicking here.",
+ "_link": "https://gitter.im/adaptlearning/adapt_framework"
+ }
+ ]
+ },
+ "_start": {
+ "_isEnabled": false,
+ "_startIds": [
+ {
+ "_id": "co-05",
+ "_skipIfComplete": true,
+ "_className": ""
+ }
+ ],
+ "_force": false,
+ "_isMenuDisabled": false
+ },
+ "_assessment": {
+ "_isPercentageBased": true,
+ "_scoreToPass": 75,
+ "_correctToPass": 75
+ },
+ "_bookmarking": {
+ "_isEnabled": true,
+ "_level": "component",
+ "title": "Resume?",
+ "body": "Would you like to resume the course from the location you were at last time?",
+ "_buttons": {
+ "yes": "Yes",
+ "no": "No"
+ }
+ }
+}
diff --git a/src/course/en/images/accordion-full.png b/build/course/en/images/accordion-full.png
similarity index 100%
rename from src/course/en/images/accordion-full.png
rename to build/course/en/images/accordion-full.png
diff --git a/src/course/en/images/full-width.png b/build/course/en/images/full-width.png
similarity index 100%
rename from src/course/en/images/full-width.png
rename to build/course/en/images/full-width.png
diff --git a/src/course/en/images/gmcq.png b/build/course/en/images/gmcq.png
similarity index 100%
rename from src/course/en/images/gmcq.png
rename to build/course/en/images/gmcq.png
diff --git a/src/course/en/images/hotgraphic.png b/build/course/en/images/hotgraphic.png
similarity index 100%
rename from src/course/en/images/hotgraphic.png
rename to build/course/en/images/hotgraphic.png
diff --git a/src/course/en/images/menu-item.png b/build/course/en/images/menu-item.png
similarity index 100%
rename from src/course/en/images/menu-item.png
rename to build/course/en/images/menu-item.png
diff --git a/src/course/en/images/narrative.png b/build/course/en/images/narrative.png
similarity index 100%
rename from src/course/en/images/narrative.png
rename to build/course/en/images/narrative.png
diff --git a/src/course/en/images/single-width.png b/build/course/en/images/single-width.png
similarity index 100%
rename from src/course/en/images/single-width.png
rename to build/course/en/images/single-width.png
diff --git a/src/course/en/images/vanilla-swatch.jpg b/build/course/en/images/vanilla-swatch.jpg
similarity index 100%
rename from src/course/en/images/vanilla-swatch.jpg
rename to build/course/en/images/vanilla-swatch.jpg
diff --git a/src/course/en/video/c-40.jpg b/build/course/en/video/c-40.jpg
similarity index 100%
rename from src/course/en/video/c-40.jpg
rename to build/course/en/video/c-40.jpg
diff --git a/src/course/en/video/c-40.mp4 b/build/course/en/video/c-40.mp4
similarity index 100%
rename from src/course/en/video/c-40.mp4
rename to build/course/en/video/c-40.mp4
diff --git a/src/course/en/video/c-40.vtt b/build/course/en/video/c-40.vtt
similarity index 100%
rename from src/course/en/video/c-40.vtt
rename to build/course/en/video/c-40.vtt
diff --git a/gitmodules.js b/gitmodules.js
deleted file mode 100644
index 25039e2ef..000000000
--- a/gitmodules.js
+++ /dev/null
@@ -1,82 +0,0 @@
-const ChildProcess = require('child_process');
-const fs = require('fs');
-const path = require('path');
-
-/**
- * Fetch and parse .gitmodules
- * @returns {Object}
- */
-function getModuleConfig() {
- const gitModulesPath = path.join(__dirname, './.gitmodules');
- if (!fs.existsSync(gitModulesPath)) return false;
- const boundaryRegExp = /\[submodule\s+"(.*?)"\]/;
- const propertyRegExp = /(.*?)\s*=\s*(.*)/;
- const str = fs.readFileSync(gitModulesPath).toString();
- const lines = str.split('\n');
- const ret = {};
- for (let i = 0; i < lines.length; i++) {
- const line = lines[i];
- const result = line.match(boundaryRegExp);
- if (!result || result.length < 2) continue;
- const submodule = result[1];
- const obj = {};
- if (!submodule) continue;
- let subline = lines[++i];
- while (subline && subline.trim()) {
- subline = lines[i];
- if (!subline || subline.match(boundaryRegExp)) {
- i--;
- break;
- }
- subline = subline.trim();
- const result = subline.match(propertyRegExp);
- const key = result[1];
- const value = result[2];
- if (key && value) obj[key] = value;
- i++;
- }
- ret[submodule] = obj;
- }
- return ret;
-}
-
-const modules = getModuleConfig();
-if (!modules) process.exit();
-
-// Fix PATH environment variables for git bash in both the terminal and AAT
-const GITCOMPATIBILITY = `${process.env.PROGRAMFILES}\\Git\\bin;${process.env.PROGRAMFILES}\\Git\\mingw64\\libexec\\git-core;${process.env.APPDATA}\\..\\Local\\Programs\\Git\\mingw64\\libexec\\git-core`;
-const env = Object.assign({}, process.env, {
- Path: `${process.env.Path};${GITCOMPATIBILITY}`,
- PATH: `${process.env.PATH};${GITCOMPATIBILITY}`,
-});
-
-// Clone submodules
-for (const subPath in modules) {
- const dirPath = path.join(__dirname, subPath);
- const url = modules[subPath].url;
- const hasPackageJSON = fs.existsSync(path.join(dirPath, 'package.json'));
- if (hasPackageJSON) continue;
- console.log(`Cleaning ${subPath}`);
- // rmSync was introduced in node v14.14.0
- fs[fs.rmSync ? 'rmSync' : 'rmdirSync'](dirPath, { recursive: true, force: true });
- console.log(`Cloning submodule ${url} into ${subPath}`);
- ChildProcess.execSync(`git clone ${url} ${subPath}`, {
- cwd: __dirname,
- env,
- stdio: 'inherit'
- });
-}
-
-// Ensure submodules are on the appropriate branch
-for (const subPath in modules) {
- const dirPath = path.join(__dirname, subPath);
- const branch = (modules[subPath].installBranch || modules[subPath].branch);
- const hasGit = fs.existsSync(path.join(dirPath, '.git'));
- if (!hasGit) continue;
- console.log(`Switching submodule ${subPath} to branch ${branch}`);
- ChildProcess.execSync(`git checkout ${branch}`, {
- cwd: dirPath,
- env,
- stdio: 'inherit'
- });
-}
diff --git a/grunt/.eslintrc b/grunt/.eslintrc
deleted file mode 100644
index 814778fda..000000000
--- a/grunt/.eslintrc
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "env": {
- "es6": true
- }
-}
\ No newline at end of file
diff --git a/grunt/config/build-config.js b/grunt/config/build-config.js
index 26a99aaf0..89662323c 100644
--- a/grunt/config/build-config.js
+++ b/grunt/config/build-config.js
@@ -5,7 +5,7 @@ module.exports = function(grunt, options) {
return grunt.config('helpers').includedFilter(filepath);
},
allowedProperties: {
- bower: [
+ package: [
'name',
'version',
'framework',
@@ -18,13 +18,6 @@ module.exports = function(grunt, options) {
'main',
'keywords',
'licence'
- ],
- package: [
- 'name',
- 'version',
- 'description',
- 'repository',
- 'license'
]
}
}
diff --git a/grunt/config/clean.js b/grunt/config/clean.js
index 0f7a94bb7..182925e3e 100644
--- a/grunt/config/clean.js
+++ b/grunt/config/clean.js
@@ -1,13 +1,6 @@
module.exports = {
dist: {
src: [
- '<%= sourcedir %>components/components.js',
- '<%= sourcedir %>extensions/extensions.js',
- '<%= sourcedir %>menu/menu.js',
- '<%= sourcedir %>theme/theme.js',
- '<%= sourcedir %>less',
- '<%= sourcedir %>templates',
- '<%= sourcedir %>plugins.js',
'<%= outputdir %>adapt/js/adapt.min.js.map',
'<%= outputdir %>.cache'
]
@@ -15,6 +8,7 @@ module.exports = {
output: {
src: [
'<%= outputdir %>/*',
+ '!<%= outputdir %>/course',
'!<%= outputdir %>.cache'
]
},
diff --git a/grunt/config/copy.js b/grunt/config/copy.js
index d4fcad23d..50054620a 100644
--- a/grunt/config/copy.js
+++ b/grunt/config/copy.js
@@ -23,171 +23,28 @@ module.exports = function(grunt, options) {
return collatedFilePath;
};
- const nonServerTasks = {
- courseAssets: {
- files: [
- {
- expand: true,
- src: ['<%= languages %>/**/*', '!**/*.<%= jsonext %>'],
- cwd: '<%= sourcedir %><%= coursedir %>/',
- dest: '<%= outputdir %><%= coursedir %>/'
- }
- ]
- },
- courseJson: {
- files: [
- {
- expand: true,
- src: ['<%= languages %>/*.<%= jsonext %>', '*.<%= jsonext %>'],
- cwd: '<%= sourcedir %><%= coursedir %>/',
- dest: '<%= outputdir %><%= coursedir %>/'
- }
- ]
- }
- };
-
const mandatoryTasks = {
- coreAssets: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>core/assets/**'],
- dest: '<%= outputdir %>assets/',
- rename: _.partial(collate, 'assets')
- }
- ]
- },
- componentAssets: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>components/*/assets/**'],
- dest: '<%= outputdir %>assets/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'assets')
- }
- ]
- },
- componentFonts: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>components/*/fonts/**'],
- dest: '<%= outputdir %>fonts/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'fonts')
- }
- ]
- },
- extensionAssets: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>extensions/*/assets/**'],
- dest: '<%= outputdir %>assets/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'assets')
- }
- ]
- },
- extensionFonts: {
+ assets: {
files: [
{
expand: true,
- src: ['<%= sourcedir %>extensions/*/fonts/**'],
- dest: '<%= outputdir %>fonts/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'fonts')
- }
- ]
- },
- menuAssets: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>menu/<%= menu %>/assets/**'],
- dest: '<%= outputdir %>assets/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'assets')
- }
- ]
- },
- coreFonts: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>core/fonts/**'],
- dest: '<%= outputdir %>fonts/',
- filter: 'isFile',
- rename: _.partial(collate, 'fonts')
- }
- ]
- },
- menuFonts: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>menu/<%= menu %>/fonts/**'],
- dest: '<%= outputdir %>fonts/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'fonts')
- }
- ]
- },
- themeAssets: {
- files: [
- {
- expand: true,
- src: ['<%= sourcedir %>theme/<%= theme %>/assets/**'],
+ src: ['<%= sourcedir %>node_modules/adapt-*/assets/**'],
dest: '<%= outputdir %>assets/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'assets')
+ filter: filepath => grunt.option('helpers').includedFilter(filepath),
+ rename: _.partial(collate, 'assets'),
+ order: grunt.option('helpers').orderFilesByPluginType
}
]
},
- themeFonts: {
+ fonts: {
files: [
{
expand: true,
- src: ['<%= sourcedir %>theme/<%= theme %>/fonts/**'],
+ src: ['<%= sourcedir %>node_modules/adapt-*/fonts/**'],
dest: '<%= outputdir %>fonts/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'fonts')
- }
- ]
- },
- coreLibraries: {
- files: [
- {
- expand: true,
- src: ['core/libraries/**/*'],
- cwd: '<%= sourcedir %>',
- dest: '<%= outputdir %>libraries/',
- rename: _.partial(collate, 'libraries')
+ filter: filepath => grunt.option('helpers').includedFilter(filepath),
+ rename: _.partial(collate, 'fonts'),
+ order: grunt.option('helpers').orderFilesByPluginType
}
]
},
@@ -195,25 +52,11 @@ module.exports = function(grunt, options) {
files: [
{
expand: true,
- src: ['components/*/libraries/**/*', 'extensions/*/libraries/**/*', 'menu/<%= menu %>/libraries/**/*', 'theme/<%= theme %>/libraries/**/*'],
- cwd: '<%= sourcedir %>',
+ src: ['<%= sourcedir %>node_modules/adapt-*/libraries/**'],
dest: '<%= outputdir %>libraries/',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'libraries')
- }
- ]
- },
- coreRequired: {
- files: [
- {
- expand: true,
- src: ['core/required/**'],
- cwd: '<%= sourcedir %>',
- dest: '<%= outputdir %>',
- rename: _.partial(collate, 'required')
+ filter: filepath => grunt.option('helpers').includedFilter(filepath),
+ rename: _.partial(collate, 'libraries'),
+ order: grunt.option('helpers').orderFilesByPluginType
}
]
},
@@ -221,21 +64,16 @@ module.exports = function(grunt, options) {
files: [
{
expand: true,
- src: ['components/*/required/**/*', 'extensions/*/required/**/*', 'menu/<%= menu %>/required/**/*', 'theme/<%= theme %>/required/**/*'],
- cwd: '<%= sourcedir %>',
- dest: '<%= outputdir %>',
- filter: function(filepath) {
- return grunt.config('helpers')
- .includedFilter(filepath);
- },
- rename: _.partial(collate, 'required')
+ src: ['<%= sourcedir %>node_modules/adapt-*/required/**'],
+ dest: '<%= outputdir %>/',
+ filter: filepath => grunt.option('helpers').includedFilter(filepath),
+ rename: _.partial(collate, 'required'),
+ order: grunt.option('helpers').orderFilesByPluginType
}
]
}
};
- if (grunt.option('outputdir')) return mandatoryTasks;
-
- return _.extend({}, nonServerTasks, mandatoryTasks);
+ return mandatoryTasks;
};
diff --git a/grunt/config/handlebars.js b/grunt/config/handlebars.js
index 52683e1a6..f308b35d6 100644
--- a/grunt/config/handlebars.js
+++ b/grunt/config/handlebars.js
@@ -17,22 +17,18 @@ module.exports = function(grunt) {
files: [
{
src: [
- '<%= sourcedir %>core/templates/**/*.hbs',
- '<%= sourcedir %>components/*/templates/**/*.hbs',
- '<%= sourcedir %>extensions/*/templates/**/*.hbs',
- '<%= sourcedir %>menu/<%= menu %>/**/*.hbs',
- '<%= sourcedir %>theme/<%= theme %>/**/*.hbs'
+ '<%= sourcedir %>/node_modules/adapt-*/templates/**/*.hbs'
],
follow: true,
dest: '<%= outputdir %>templates.js',
filter: function(filepath) {
- if (filepath.indexOf(path.join(grunt.config('sourcedir'), 'core')) > -1) {
+ if (filepath?.indexOf(path.join(grunt.config('sourcedir'), 'adapt-contrib-core')) > -1) {
// Always include core templates.
return true;
}
-
- return grunt.config('helpers').includedFilter(filepath);
- }
+ return grunt.option('helpers').includedFilter(filepath);
+ },
+ order: grunt.option('helpers').orderFilesByPluginType
}
]
}
diff --git a/grunt/config/javascript.js b/grunt/config/javascript.js
index 204ac6d41..758d720f1 100644
--- a/grunt/config/javascript.js
+++ b/grunt/config/javascript.js
@@ -2,136 +2,44 @@ module.exports = function(grunt, options) {
return {
dev: {
options: {
- name: 'core/js/app',
- baseUrl: '<%= sourcedir %>',
+ name: 'adapt-contrib-core/js/app',
+ baseUrl: '<%= sourcedir %>node_modules/',
out: '<%= outputdir %>adapt/js/adapt.min.js',
- cachePath: '<%= outputdir %>.cache',
// fetch these bower plugins an add them as dependencies to the app.js
plugins: [
- '<%= sourcedir %>components/*/bower.json',
- '<%= sourcedir %>extensions/*/bower.json',
- '<%= sourcedir %>menu/<%= menu %>/bower.json',
- '<%= sourcedir %>theme/<%= theme %>/bower.json'
- ],
- pluginsPath: '<%= sourcedir %>plugins.js',
- pluginsModule: 'plugins',
- pluginsFilter: function(filepath) {
- return grunt.config('helpers').includedFilter(filepath);
- },
- umdImports: [
+ '<%= sourcedir %>/node_modules/adapt-*/package.json',
+ '<%= sourcedir %>/node_modules/adapt-*/bower.json'
],
+ pluginsFilter: filepath => grunt.option('helpers').includedFilter(filepath),
+ pluginsOrder: grunt.option('helpers').orderFilesByPluginType,
reactTemplates: [
- '<%= sourcedir %>core/templates/**/*.jsx',
- '<%= sourcedir %>components/*/templates/**/*.jsx',
- '<%= sourcedir %>extensions/*/templates/**/*.jsx',
- '<%= sourcedir %>menu/*/templates/**/*.jsx',
- '<%= sourcedir %>theme/*/templates/**/*.jsx'
+ '<%= sourcedir %>node_modules/adapt-*/templates/**/*.jsx'
],
- externalMap: {
- '.*/libraries/(?!mediaelement-fullscreen-hook)+': 'libraries/'
- },
- external: {
- jquery: 'empty:',
- underscore: 'empty:',
- backbone: 'empty:',
- modernizr: 'empty:',
- handlebars: 'empty:',
- velocity: 'empty:',
- imageReady: 'empty:',
- inview: 'empty:',
- a11y: 'empty:',
- scrollTo: 'empty:',
- libraries: 'empty:',
- bowser: 'empty:',
- 'core/js/libraries/bowser': 'empty:',
- 'coreJS/libraries/bowser': 'empty:',
- react: 'empty:',
- 'react-dom': 'empty:',
- 'object.assign': 'empty:',
- 'html-react-parser': 'empty:',
- semver: 'empty:'
- },
- map: {
- coreJS: 'core/js',
- coreViews: 'core/js/views',
- coreModels: 'core/js/models',
- coreCollections: 'core/js/collections',
- coreHelpers: 'core/js/helpers',
- // This library from the media component has a circular reference to core/js/adapt, it should be loaded after Adapt
- // It needs to be moved from the libraries folder to the js folder
- 'libraries/mediaelement-fullscreen-hook': '../libraries/mediaelement-fullscreen-hook'
- },
generateSourceMaps: true
},
// newer configuration
files: {
'<%= outputdir %>adapt/js/adapt.min.js': [
- '<%= sourcedir %>/**/*.js',
- '<%= sourcedir %>/**/*.jsx'
+ '<%= sourcedir %>/node_modules/adapt-*/**/*.js',
+ '<%= sourcedir %>/node_modules/adapt-*/**/*.jsx'
]
}
},
compile: {
options: {
- name: 'core/js/app',
- baseUrl: '<%= sourcedir %>',
+ name: 'adapt-contrib-core/js/app',
+ baseUrl: '<%= sourcedir %>node_modules/',
out: '<%= outputdir %>adapt/js/adapt.min.js',
- cachePath: '<%= outputdir %>.cache',
// fetch these bower plugins an add them as dependencies to the app.js
plugins: [
- '<%= sourcedir %>components/*/bower.json',
- '<%= sourcedir %>extensions/*/bower.json',
- '<%= sourcedir %>menu/<%= menu %>/bower.json',
- '<%= sourcedir %>theme/<%= theme %>/bower.json'
- ],
- pluginsPath: '<%= sourcedir %>/plugins.js',
- pluginsModule: 'plugins',
- pluginsFilter: function(filepath) {
- return grunt.config('helpers').includedFilter(filepath);
- },
- umdImports: [
+ '<%= sourcedir %>/node_modules/adapt-*/package.json',
+ '<%= sourcedir %>/node_modules/adapt-*/bower.json'
],
+ pluginsFilter: filepath => grunt.option('helpers').includedFilter(filepath),
+ pluginsOrder: grunt.option('helpers').orderFilesByPluginType,
reactTemplates: [
- '<%= sourcedir %>core/templates/**/*.jsx',
- '<%= sourcedir %>components/*/templates/**/*.jsx',
- '<%= sourcedir %>extensions/*/templates/**/*.jsx',
- '<%= sourcedir %>menu/*/templates/**/*.jsx',
- '<%= sourcedir %>theme/*/templates/**/*.jsx'
- ],
- externalMap: {
- '.*/libraries/(?!mediaelement-fullscreen-hook)+': 'libraries/'
- },
- external: {
- jquery: 'empty:',
- underscore: 'empty:',
- backbone: 'empty:',
- modernizr: 'empty:',
- handlebars: 'empty:',
- velocity: 'empty:',
- imageReady: 'empty:',
- inview: 'empty:',
- a11y: 'empty:',
- scrollTo: 'empty:',
- libraries: 'empty:',
- bowser: 'empty:',
- 'core/js/libraries/bowser': 'empty:',
- 'coreJS/libraries/bowser': 'empty:',
- react: 'empty:',
- 'react-dom': 'empty:',
- 'object.assign': 'empty:',
- 'html-react-parser': 'empty:',
- semver: 'empty:'
- },
- map: {
- coreJS: 'core/js',
- coreViews: 'core/js/views',
- coreModels: 'core/js/models',
- coreCollections: 'core/js/collections',
- coreHelpers: 'core/js/helpers',
- // This library from the media component has a circular reference to core/js/adapt, it should be loaded after Adapt
- // It needs to be moved from the libraries folder to the js folder
- 'libraries/mediaelement-fullscreen-hook': '../libraries/mediaelement-fullscreen-hook'
- }
+ '<%= sourcedir %>node_modules/adapt-*/templates/**/*.jsx'
+ ]
}
}
};
diff --git a/grunt/config/jsonlint.js b/grunt/config/jsonlint.js
index 3764fa272..4e5fd6c22 100644
--- a/grunt/config/jsonlint.js
+++ b/grunt/config/jsonlint.js
@@ -1,3 +1,3 @@
module.exports = {
- src: ['<%= sourcedir %><%= coursedir %>/<%=languages%>/*.<%=jsonext%>']
+ src: ['<%= outputdir %><%= coursedir %>/<%=languages%>/*.<%=jsonext%>']
};
diff --git a/grunt/config/less.js b/grunt/config/less.js
index c5961276c..d52d7ccef 100644
--- a/grunt/config/less.js
+++ b/grunt/config/less.js
@@ -39,23 +39,22 @@ module.exports = function(grunt, options) {
filepaths = filepaths.map(function(path) {
return path.replace(convertSlashes, '/');
});
- return filepaths.sort(compareFilePaths);
+ filepaths = filepaths.sort(compareFilePaths);
+ filepaths = grunt.option('helpers').orderFilesByPluginType(filepaths);
+ return filepaths;
}
function includedFilter(filepath) {
- return grunt.config('helpers').includedFilter(filepath);
+ return grunt.option('helpers').includedFilter(filepath);
}
const compileOptions = {
baseUrl: '<%= sourcedir %>',
mandatory: [
- '<%= sourcedir %>core/less/**/*.less'
+ '<%= sourcedir %>node_modules/adapt-contrib-core/less/**/*.less'
],
src: [
- '<%= sourcedir %>components/*/less/**/*.less',
- '<%= sourcedir %>extensions/*/less/**/*.less',
- '<%= sourcedir %>menu/<%= menu %>/**/*.less',
- '<%= sourcedir %>theme/<%= theme %>/**/*.less'
+ '<%= sourcedir %>node_modules/adapt-*/less/**/*.less'
],
config: '<%= outputdir %><%= coursedir %>/config.<%= jsonext %>',
dest: '<%= outputdir %>',
@@ -75,7 +74,7 @@ module.exports = function(grunt, options) {
};
const devOptions = {
- ...compileOptions,
+ ...compileOptions,
...{ sourcemaps: true, compress: false }
};
diff --git a/grunt/config/scripts.js b/grunt/config/scripts.js
index 128d81ce8..faf363372 100644
--- a/grunt/config/scripts.js
+++ b/grunt/config/scripts.js
@@ -4,14 +4,12 @@ module.exports = function(grunt, options) {
outputdir: '<%= outputdir %>',
sourcedir: '<%= sourcedir %>',
plugins: [
- '<%= sourcedir %>components/*/bower.json',
- '<%= sourcedir %>extensions/*/bower.json',
- '<%= sourcedir %>menu/<%= menu %>/bower.json',
- '<%= sourcedir %>theme/<%= theme %>/bower.json'
+ '<%= sourcedir %>node_modules/adapt-*/package.json'
],
pluginsFilter: function(filepath) {
- return grunt.config('helpers').includedFilter(filepath) && grunt.config('helpers').scriptSafeFilter(filepath);
- }
+ return grunt.option('helpers').includedFilter(filepath) && grunt.option('helpers').scriptSafeFilter(filepath);
+ },
+ pluginsOrder: grunt.option('helpers').orderFilesByPluginType
}
};
};
diff --git a/grunt/config/watch.js b/grunt/config/watch.js
index 989018dcd..5d6a0bc47 100644
--- a/grunt/config/watch.js
+++ b/grunt/config/watch.js
@@ -1,74 +1,43 @@
-// TODO excludes
module.exports = {
- bowerJson: {
- files: ['<%= sourcedir %>*/*/bower.json'],
+ courseJson: {
+ files: ['<%= outputdir %><%= coursedir %>/**/*.<%= jsonext %>', '<%= outputdir %><%= coursedir %>/*/language_data_manifest.js', '<%= sourcedir %>/node_modules/adapt-*/**/*.schema', '<%= sourcedir %>/node_modules/adapt-*/**/*.schema.json'],
+ tasks: ['language-data-manifests', 'jsonlint', 'check-json', 'schema-defaults']
+ },
+ packageJson: {
+ files: ['<%= sourcedir %>/node_modules/adapt-*/package.json', '<%= sourcedir %>/node_modules/adapt-*/bower.json'],
tasks: ['dev']
},
scripts: {
- files: ['<%= sourcedir %>*/*/scripts/*'],
+ files: ['<%= sourcedir %>/node_modules/adapt-*/scripts/**/*.js'],
tasks: ['dev']
},
less: {
- files: ['<%= sourcedir %>**/*.less'],
+ files: ['<%= sourcedir %>/node_modules/adapt-*/**/*.less'],
tasks: ['less:dev']
},
handlebars: {
- files: ['<%= sourcedir %>**/*.hbs'],
+ files: ['<%= sourcedir %>/node_modules/adapt-*/**/*.hbs'],
tasks: ['handlebars', 'javascript:dev']
},
- courseJson: {
- files: ['<%= sourcedir %><%= coursedir %>/**/*.<%= jsonext %>', '<%= outputdir %><%= coursedir %>/*/language_data_manifest.js'],
- tasks: ['language-data-manifests', 'jsonlint', 'check-json', 'newer:copy:courseJson', 'schema-defaults']
- },
- courseAssets: {
- files: ['<%= sourcedir %><%= coursedir %>/<%=languages%>/*', '!<%= sourcedir %><%= coursedir %>/<%=languages%>/*.<%= jsonext %>'],
- tasks: ['newer:copy:courseAssets']
- },
js: {
- files: ['<%= sourcedir %>**/*.js', '<%= sourcedir %>**/*.jsx'],
- options: {
- spawn: false
- },
+ files: ['<%= sourcedir %>/node_modules/adapt-*/**/*.js', '<%= sourcedir %>/node_modules/adapt-*/**/*.jsx'],
+ options: { spawn: false },
tasks: ['javascript:dev', 'clean:temp']
},
- componentsAssets: {
- files: ['<%= sourcedir %>components/**/assets/**'],
- tasks: ['newer:copy:componentAssets']
- },
- componentsFonts: {
- files: ['<%= sourcedir %>components/**/fonts/**'],
- tasks: ['newer:copy:componentFonts']
- },
- extensionsAssets: {
- files: ['<%= sourcedir %>extensions/**/assets/**'],
- tasks: ['newer:copy:extensionAssets']
- },
- extensionsFonts: {
- files: ['<%= sourcedir %>extensions/**/fonts/**'],
- tasks: ['newer:copy:extensionFonts']
- },
- menuAssets: {
- files: ['<%= sourcedir %>menu/<%= menu %>/**/assets/**'],
- tasks: ['newer:copy:menuAssets']
- },
- menuFonts: {
- files: ['<%= sourcedir %>menu/<%= menu %>/**/fonts/**'],
- tasks: ['newer:copy:menuFonts']
- },
- themeAssets: {
- files: ['<%= sourcedir %>theme/<%= theme %>/**/assets/**'],
- tasks: ['newer:copy:themeAssets']
+ assets: {
+ files: ['<%= sourcedir %>/node_modules/adapt-*/assets/**'],
+ tasks: ['newer:copy:assets']
},
- themeFonts: {
- files: ['<%= sourcedir %>theme/<%= theme %>/**/fonts/**'],
- tasks: ['newer:copy:themeFonts']
+ fonts: {
+ files: ['<%= sourcedir %>/node_modules/adapt-*/fonts/**'],
+ tasks: ['newer:copy:fonts']
},
libraries: {
- files: ['<%= sourcedir %>core/libraries/**/*', '<%= sourcedir %>*/*/libraries/**/*'],
+ files: ['<%= sourcedir %>/node_modules/adapt-*/libraries/**'],
tasks: ['newer:copy:libraries']
},
required: {
- files: ['<%= sourcedir %>core/required/**/*', '<%= sourcedir %>*/*/required/**/*'],
+ files: ['<%= sourcedir %>/node_modules/adapt-*/required/**'],
tasks: ['newer:copy:required']
}
};
diff --git a/grunt/helpers.js b/grunt/helpers.js
index a874dba3a..3323592ba 100644
--- a/grunt/helpers.js
+++ b/grunt/helpers.js
@@ -1,13 +1,13 @@
-var _ = require('underscore');
-var fs = require('fs-extra');
-var path = require('path');
+const _ = require('underscore');
+const fs = require('fs-extra');
+const path = require('path');
// extends grunt.file.expand with { order: cb(filePaths) }
require('grunt-file-order');
const Framework = require('./helpers/Framework');
module.exports = function(grunt) {
- var convertSlashes = /\\/g;
+ const convertSlashes = /\\/g;
// grunt tasks
@@ -15,22 +15,22 @@ module.exports = function(grunt) {
grunt.log.ok(`Starting server in '${grunt.config('outputdir')}' using port ${grunt.config('connect.server.options.port')}`);
});
grunt.registerTask('_log-vars', 'Logs out user-defined build variables', function() {
- var includes = grunt.config('includes');
- var excludes = grunt.config('excludes');
- var productionExcludes = grunt.config('productionExcludes');
+ const includes = grunt.config('includes');
+ const excludes = grunt.config('excludes');
+ const productionExcludes = grunt.config('productionExcludes');
if (includes && excludes) {
grunt.fail.fatal('Cannot specify includes and excludes. Please check your config.json configuration.');
}
if (includes) {
- let count = includes.length;
+ const count = includes.length;
grunt.log.writeln('The following will be included in the build:');
for (let i = 0; i < count; i++) { grunt.log.writeln('- ' + includes[i]); }
grunt.log.writeln('');
}
if (excludes) {
- let count = excludes.length;
+ const count = excludes.length;
grunt.log.writeln('The following will be excluded from the build:');
for (let i = 0; i < count; i++) { grunt.log.writeln('- ' + excludes[i]); }
grunt.log.writeln('');
@@ -49,102 +49,85 @@ module.exports = function(grunt) {
if (grunt.config('languages') !== '**') grunt.log.ok(`The following languages will be included in the build '${grunt.config('languages')}'`);
});
- // privates
- var generateIncludedRegExp = function() {
- var includes = grunt.config('includes') || [];
- var pluginTypes = exports.defaults.pluginTypes;
-
- // Return a more specific plugin regExp including src path.
- var re = _.map(includes, function(plugin) {
- return _.map(pluginTypes, function(type) {
- // eslint-disable-next-line no-useless-escape
- return exports.defaults.sourcedir + type + '\/' + plugin + '\/';
- }).join('|');
- }).join('|');
- // eslint-disable-next-line no-useless-escape
- var core = exports.defaults.sourcedir + `core\/`;
- return new RegExp(core + '|' + re, 'i');
+ const appendSlash = function(dir) {
+ if (!dir) return;
+ const lastChar = dir.substring(dir.length - 1, dir.length);
+ if (lastChar === path.sep) return dir;
+ return dir + path.sep;
};
- var generateNestedIncludedRegExp = function() {
- var includes = grunt.config('includes') || [];
- var folderRegEx = 'less/plugins';
-
- // Return a more specific plugin regExp including src path.
- var re = _.map(includes, function(plugin) {
+ const getIncludedRegExp = function() {
+ const generateIncludedRegExp = function() {
+ const includes = grunt.config('includes') || [];
+ // TODO: resolve and add dependencies to includes from plugins
+ const pluginTypes = exports.defaults.pluginTypes;
+ // Return a more specific plugin regExp including src path.
// eslint-disable-next-line no-useless-escape
- return exports.defaults.sourcedir + '([^\/]*)\/([^\/]*)\/' + folderRegEx + '\/' + plugin + '\/';
- }).join('|');
- return new RegExp(re, 'i');
- };
-
- var generateExcludedRegExp = function() {
- var excludes = grunt.config('excludes') || [];
- if (grunt.config('type') === 'production') {
- const productionExcludes = grunt.config('productionExcludes') || [];
- excludes.push(...productionExcludes);
- }
- var pluginTypes = exports.defaults.pluginTypes;
-
- // Return a more specific plugin regExp including src path.
- var re = _.map(excludes, function(plugin) {
- return _.map(pluginTypes, function(type) {
- // eslint-disable-next-line no-useless-escape
- return exports.defaults.sourcedir + type + '\/' + plugin + '\/';
- }).join('|');
- }).join('|');
- return new RegExp(re, 'i');
- };
-
- var generateScriptSafeRegExp = function() {
- let includes = grunt.config('scriptSafe') || [];
- let re = '';
- let count = includes.length;
-
- for (var i = 0; i < count; i++) {
+ const re = _.map(includes, plugin => _.map(pluginTypes, type => exports.defaults.sourcedir + type + '\/' + plugin + '\/').join('|')).join('|');
// eslint-disable-next-line no-useless-escape
- re += '\/' + includes[i].toLowerCase() + '\/';
- if (i < includes.length - 1) re += '|';
- }
- return new RegExp(re, 'i');
- };
-
- var appendSlash = function(dir) {
- if (dir) {
- var lastChar = dir.substring(dir.length - 1, dir.length);
- if (lastChar !== path.sep) return dir + path.sep;
- }
- };
-
- // eslint-disable-next-line no-unused-vars
- var includedProcess = function(content, filepath) {
- if (!exports.isPathIncluded(filepath)) return '';
- else return content;
- };
-
- var getIncludedRegExp = function() {
- var configValue = grunt.config('includedRegExp');
+ const core = exports.defaults.sourcedir + 'core\/';
+ return new RegExp(core + '|' + re, 'i');
+ };
+ const configValue = grunt.config('includedRegExp');
return configValue || grunt.config('includedRegExp', generateIncludedRegExp());
};
- var getNestedIncludedRegExp = function() {
- var configValue = grunt.config('nestedIncludedRegExp');
+ const getNestedIncludedRegExp = function() {
+ const generateNestedIncludedRegExp = function() {
+ const includes = grunt.config('includes') || [];
+ const folderRegEx = 'less/plugins';
+ // Return a more specific plugin regExp including src path.
+ // eslint-disable-next-line no-useless-escape
+ const re = _.map(includes, plugin => exports.defaults.sourcedir + '([^\/]*)\/([^\/]*)\/' + folderRegEx + '\/' + plugin + '\/').join('|');
+ return new RegExp(re, 'i');
+ };
+ const configValue = grunt.config('nestedIncludedRegExp');
return configValue || grunt.config('nestedIncludedRegExp', generateNestedIncludedRegExp());
};
- var getExcludedRegExp = function() {
- var configValue = grunt.config('excludedRegExp');
+ const getExcludedRegExp = function() {
+ const generateExcludedRegExp = function() {
+ const excludes = grunt.config('excludes') || [];
+ if (grunt.config('type') === 'production') {
+ const productionExcludes = grunt.config('productionExcludes') || [];
+ excludes.push(...productionExcludes);
+ }
+ const pluginTypes = exports.defaults.pluginTypes;
+ // Return a more specific plugin regExp including src path.
+ // eslint-disable-next-line no-useless-escape
+ const re = _.map(excludes, plugin => _.map(pluginTypes, type => exports.defaults.sourcedir + type + '\/' + plugin + '\/').join('|')).join('|');
+ return new RegExp(re, 'i');
+ };
+ const configValue = grunt.config('excludedRegExp');
return configValue || grunt.config('excludedRegExp', generateExcludedRegExp());
};
- var getScriptSafeRegExp = function() {
- var configValue = grunt.config('scriptSafeRegExp');
+ const getScriptSafeRegExp = function() {
+ const generateScriptSafeRegExp = function() {
+ const includes = grunt.config('scriptSafe') || [];
+ let re = '';
+ const count = includes.length;
+ for (let i = 0; i < count; i++) {
+ // eslint-disable-next-line no-useless-escape
+ re += '\/' + includes[i].toLowerCase() + '\/';
+ if (i < includes.length - 1) re += '|';
+ }
+ return new RegExp(re, 'i');
+ };
+ const configValue = grunt.config('scriptSafeRegExp');
return configValue || grunt.config('scriptSafeRegExp', generateScriptSafeRegExp());
};
+ const getSortedPluginPaths = function() {
+ const pluginsContainer = exports.getFramework().getPlugins();
+ const plugins = pluginsContainer.plugins;
+ const sortedPluginPaths = plugins.map((plugin) => plugin.sourcePath);
+ return sortedPluginPaths;
+ };
+
// exported
- var exports = {
+ const exports = {
defaults: {
sourcedir: 'src/',
@@ -159,6 +142,7 @@ module.exports = function(grunt) {
],
pluginTypes: [
+ 'core',
'components',
'extensions',
'menu',
@@ -170,18 +154,29 @@ module.exports = function(grunt) {
]
},
+ orderFilesByPluginType: function(files) {
+ if (files.length <= 1) return files;
+ const sortedPluginPaths = getSortedPluginPaths();
+ files.sort((a, b) => {
+ const aIndex = sortedPluginPaths.findIndex(pluginPath => a.startsWith(pluginPath));
+ const bIndex = sortedPluginPaths.findIndex(pluginPath => b.startsWith(pluginPath));
+ return aIndex - bIndex;
+ });
+ return files;
+ },
+
getIncludes: function(buildIncludes, configData) {
- var dependencies = [];
+ const dependencies = [];
// Iterate over the plugin types.
- for (var i = 0; i < exports.defaults.pluginTypes.length; i++) {
- var pluginTypeDir = path.join(configData.sourcedir, exports.defaults.pluginTypes[i]);
+ for (let i = 0; i < exports.defaults.pluginTypes.length; i++) {
+ const pluginTypeDir = path.join(configData.sourcedir, exports.defaults.pluginTypes[i]);
// grab a list of the installed (and included) plugins for this type
- var plugins = _.intersection(fs.readdirSync(pluginTypeDir), buildIncludes);
- for (var j = 0; j < plugins.length; j++) {
+ const plugins = _.intersection(fs.readdirSync(pluginTypeDir), buildIncludes);
+ for (let j = 0; j < plugins.length; j++) {
try {
- var bowerJson = grunt.file.readJSON(path.join(pluginTypeDir, plugins[j], 'bower.json'));
- for (var key in bowerJson.dependencies) {
+ const bowerJson = grunt.file.readJSON(path.join(pluginTypeDir, plugins[j], 'bower.json'));
+ for (const key in bowerJson.dependencies) {
if (!_.contains(buildIncludes, key)) dependencies.push(key);
}
} catch (error) {
@@ -195,16 +190,16 @@ module.exports = function(grunt) {
generateConfigData: function() {
- var root = __dirname.split(path.sep).slice(0, -1).join(path.sep);
- var adaptJSON = fs.readJSONSync(`${root}/adapt.json`);
- var sourcedir = appendSlash(grunt.option('sourcedir')) || exports.defaults.sourcedir;
- var outputdir = appendSlash(grunt.option('outputdir')) || exports.defaults.outputdir;
- var cachepath = grunt.option('cachepath') || null;
- var tempdir = outputdir + '.temp/';
- var jsonext = grunt.option('jsonext') || exports.defaults.jsonext;
- var coursedir = grunt.option('coursedir') || adaptJSON.coursedir || exports.defaults.coursedir;
+ const root = __dirname.split(path.sep).slice(0, -1).join(path.sep);
+ const adaptJSON = fs.readJSONSync(`${root}/adapt.json`);
+ const sourcedir = appendSlash(grunt.option('sourcedir')) || exports.defaults.sourcedir;
+ const outputdir = appendSlash(grunt.option('outputdir')) || exports.defaults.outputdir;
+ const cachepath = grunt.option('cachepath') || null;
+ const tempdir = outputdir + '.temp/';
+ const jsonext = grunt.option('jsonext') || exports.defaults.jsonext;
+ const coursedir = grunt.option('coursedir') || adaptJSON.coursedir || exports.defaults.coursedir;
- var languageFolders = '';
+ let languageFolders = '';
if (grunt.option('languages') && grunt.option('languages').split(',').length > 1) {
languageFolders = '{' + grunt.option('languages') + '}';
} else {
@@ -212,10 +207,10 @@ module.exports = function(grunt) {
}
// Selectively load the course.json ('outputdir' passed by server-build)
- var configDir = grunt.option('outputdir') ? outputdir : sourcedir;
+ const configDir = outputdir;
// add root path if necessary, and point to course/config.json
- var configPath = path.join(path.resolve(root, configDir), coursedir, 'config.' + jsonext);
+ const configPath = path.join(path.resolve(root, configDir), coursedir, 'config.' + jsonext);
try {
var buildConfig = grunt.file.readJSON(configPath).build || {};
@@ -226,16 +221,16 @@ module.exports = function(grunt) {
const isDevelopmentBuild = process.argv.some(arg => (arg === 'dev' || arg.includes(':dev')));
- var data = {
+ const data = {
type: isDevelopmentBuild ? 'development' : 'production',
- root: root,
- sourcedir: sourcedir,
- outputdir: outputdir,
+ root,
+ sourcedir,
+ outputdir,
configdir: configDir,
- coursedir: coursedir,
- cachepath: cachepath,
- tempdir: tempdir,
- jsonext: jsonext,
+ coursedir,
+ cachepath,
+ tempdir,
+ jsonext,
trackingIdType: grunt.option('trackingidtype') || 'block',
theme: grunt.option('theme') || exports.defaults.theme,
menu: grunt.option('menu') || exports.defaults.menu,
@@ -264,7 +259,7 @@ module.exports = function(grunt) {
includedFilter: exports.includedFilter,
jsonext: data.jsonext,
trackingIdType: data.trackingIdType,
- useOutputData: Boolean(grunt.option('outputdir')),
+ useOutputData: true,
log: grunt.log.ok,
warn: grunt.log.error
});
@@ -281,19 +276,19 @@ module.exports = function(grunt) {
* assumption: all folders are plugins
*/
getInstalledPluginsByType: function(type) {
- var pluginDir = grunt.config('sourcedir') + type + '/';
+ const pluginDir = grunt.config('sourcedir') + type + '/node_modules/';
if (!grunt.file.isDir(pluginDir)) return []; // fail silently
// return all sub-folders, and save for later
return grunt.option(type, grunt.file.expand({
filter: 'isDirectory',
cwd: pluginDir
- }, '*'));
+ }, 'adapt-*'));
},
isPluginInstalled: function(pluginName) {
- var types = ['components', 'extensions', 'theme', 'menu'];
- for (var i = 0, len = types.length; i < len; i++) {
- var plugins = grunt.option(types[i]) || this.getInstalledPluginsByType(types[i]);
+ const types = ['components', 'extensions', 'theme', 'menu'];
+ for (let i = 0, len = types.length; i < len; i++) {
+ const plugins = grunt.option(types[i]) || this.getInstalledPluginsByType(types[i]);
if (plugins.indexOf(pluginName) !== -1) return true;
}
return false;
@@ -302,16 +297,16 @@ module.exports = function(grunt) {
isPathIncluded: function(pluginPath) {
pluginPath = pluginPath.replace(convertSlashes, '/');
- var includes = grunt.config('includes');
- var excludes = grunt.config('excludes') || (grunt.config('type') === 'production' && grunt.config('productionExcludes'));
-
+ const includes = grunt.config('includes');
+ const excludes = grunt.config('excludes') || (grunt.config('type') === 'production' && grunt.config('productionExcludes'));
// carry on as normal if no includes/excludes
if (!includes && !excludes) return true;
// Very basic check to see if the file path string contains any
// of the included list of plugin string names.
- var isIncluded = includes && pluginPath.search(getIncludedRegExp()) !== -1;
- var isExcluded = excludes && pluginPath.search(getExcludedRegExp()) !== -1;
+
+ const isIncluded = includes && pluginPath.search(getIncludedRegExp()) !== -1;
+ const isExcluded = excludes && pluginPath.search(getExcludedRegExp()) !== -1;
// Exclude any plugins that don't match any part of the full file path string.
if (isExcluded || isIncluded === false) {
@@ -321,13 +316,13 @@ module.exports = function(grunt) {
// Check the LESS plugins folder exists.
// The LESS 'plugins' folder doesn't exist, so add the file,
// as the plugin has already been found in the previous check.
- var nestedPluginsPath = !!pluginPath.match(/(?:.)+(?:\/less\/plugins)/g);
+ const nestedPluginsPath = !!pluginPath.match(/(?:.)+(?:\/less\/plugins)/g);
if (!nestedPluginsPath) {
return true;
}
// The LESS 'plugins' folder exists, so check that any plugins in this folder are allowed.
- var hasPluginSubDirectory = !!pluginPath.match(getNestedIncludedRegExp());
+ const hasPluginSubDirectory = !!pluginPath.match(getNestedIncludedRegExp());
if (hasPluginSubDirectory) {
return true;
}
@@ -340,9 +335,9 @@ module.exports = function(grunt) {
isPluginScriptSafe: function(pluginPath) {
pluginPath = pluginPath.replace(convertSlashes, '/');
- var includes = grunt.config('scriptSafe');
- var isExplicitlyDefined = (includes && pluginPath.search(getScriptSafeRegExp()) !== -1);
- var isIncluded = grunt.option('allowscripts') || includes[0] === '*' || isExplicitlyDefined;
+ const includes = grunt.config('scriptSafe');
+ const isExplicitlyDefined = (includes && pluginPath.search(getScriptSafeRegExp()) !== -1);
+ const isIncluded = grunt.option('allowscripts') || includes[0] === '*' || isExplicitlyDefined;
return isIncluded;
@@ -357,7 +352,7 @@ module.exports = function(grunt) {
},
/** @returns {Framework} */
- getFramework: function({ useOutputData = Boolean(grunt.option('outputdir')) } = {}) {
+ getFramework: function() {
const buildConfig = exports.generateConfigData();
const framework = new Framework({
rootPath: buildConfig.root,
@@ -367,7 +362,7 @@ module.exports = function(grunt) {
includedFilter: exports.includedFilter,
jsonext: buildConfig.jsonext,
trackingIdType: buildConfig.trackingIdType,
- useOutputData,
+ useOutputData: true,
log: grunt.log.ok,
warn: grunt.log.error
});
diff --git a/grunt/helpers/Framework.js b/grunt/helpers/Framework.js
index 5f547e9a4..0f258f262 100644
--- a/grunt/helpers/Framework.js
+++ b/grunt/helpers/Framework.js
@@ -35,7 +35,7 @@ class Framework {
includedFilter = function() { return true; },
jsonext = 'json',
trackingIdType = 'block',
- useOutputData = false,
+ useOutputData = true,
log = console.log,
warn = console.warn
} = {}) {
@@ -89,12 +89,10 @@ class Framework {
* function or the Framework instance.
* @returns {Data}
*/
- getData({
- useOutputData = this.useOutputData
- } = {}) {
+ getData() {
const data = new Data({
framework: this,
- sourcePath: useOutputData ? this.outputPath : this.sourcePath,
+ sourcePath: this.outputPath,
courseDir: this.courseDir,
jsonext: this.jsonext,
trackingIdType: this.trackingIdType,
@@ -106,7 +104,8 @@ class Framework {
/** @returns {Plugins} */
getPlugins({
- includedFilter = this.includedFilter
+ includedFilter = this.includedFilter,
+ sortBy = 'type'
} = {}) {
const plugins = new Plugins({
framework: this.framework,
@@ -115,7 +114,7 @@ class Framework {
log: this.log,
warn: this.warn
});
- plugins.load();
+ plugins.load().sortBy(sortBy);
return plugins;
}
diff --git a/grunt/helpers/Plugins.js b/grunt/helpers/Plugins.js
index 02f9a3f33..bfb51bc4f 100644
--- a/grunt/helpers/Plugins.js
+++ b/grunt/helpers/Plugins.js
@@ -49,8 +49,7 @@ class Plugins {
*/
get pluginLocations() {
return [
- `${this.sourcePath}core/`,
- `${this.sourcePath}!(core|${this.courseDir})/*/`
+ `${this.sourcePath}node_modules/adapt-*`
];
}
@@ -58,20 +57,31 @@ class Plugins {
load() {
this.plugins = globs.sync(this.pluginLocations).map(sourcePath => {
if (!this.includedFilter(sourcePath)) {
- return;
+ return null;
}
- let plugin = new Plugin({
+ const plugin = new Plugin({
framework: this.framework,
- sourcePath,
+ sourcePath: sourcePath + '/',
log: this.log,
warn: this.warn
});
plugin.load();
+ if (!plugin.isAdaptPlugin) return null;
return plugin;
}).filter(Boolean);
return this;
}
+ sortBy(by = 'type') {
+ switch (by) {
+ case 'type': {
+ const types = { core: 1, component: 2, extension: 3, menu: 4, theme: 5 };
+ this.plugins.sort((a, b) => (types[a.type] || 0) - (types[b.type] || 0));
+ }
+ }
+ return this;
+ }
+
/** @returns {JSONFileItem} */
getAllPackageJSONFileItems() {
return this.plugins.reduce((items, plugin) => {
diff --git a/grunt/helpers/Schemas.js b/grunt/helpers/Schemas.js
index 11c51c077..7efb3a9b5 100644
--- a/grunt/helpers/Schemas.js
+++ b/grunt/helpers/Schemas.js
@@ -66,7 +66,7 @@ class Schemas {
const json = fs.readJSONSync(filePath);
const isExtensionSchema = Boolean(json.properties.pluginLocations);
const InferredSchemaClass = (isExtensionSchema ? ExtensionSchema : ModelSchema);
- const inferredSchemaName = (plugin.name === 'core') ?
+ const inferredSchemaName = (plugin.name === 'core' || plugin.name === 'adapt-contrib-core') ?
path.parse(filePath).name.split('.')[0] : // if core, get schema name from file name
isExtensionSchema ?
plugin.name : // assume schema name is plugin name
@@ -137,7 +137,7 @@ class Schemas {
if (!extensionParts) {
return;
}
- for (let modelName in extensionParts) {
+ for (const modelName in extensionParts) {
const extensionPart = extensionParts[modelName];
/**
* Check if the sub-schema part has any defined properties.
@@ -187,7 +187,7 @@ class Schemas {
* @returns {ModelSchemas}
*/
getSchemasForModelJSON(json) {
- let schemas = [];
+ const schemas = [];
if (json._type) {
if (json._type === 'menu' || json._type === 'page') {
schemas.push(this.getModelSchemaByName('contentobject'));
diff --git a/grunt/helpers/Translate.js b/grunt/helpers/Translate.js
index 1117a877c..2122de9b7 100644
--- a/grunt/helpers/Translate.js
+++ b/grunt/helpers/Translate.js
@@ -48,7 +48,7 @@ class Translate {
languagePath = path.join(process.cwd(), 'languagefiles'),
outputPath = '',
courseDir = 'course',
- useOutputData = false,
+ useOutputData = true,
isTest = false,
log = console.log,
warn = console.warn
@@ -94,7 +94,7 @@ class Translate {
load() {
this.data = new Data({
framework: this.framework,
- sourcePath: this.useOutputData ? this.outputPath : this.sourcePath,
+ sourcePath: this.outputPath,
courseDir: this.courseDir,
jsonext: this.jsonext,
trackingIdType: this.framework.trackingIdType,
@@ -136,16 +136,16 @@ class Translate {
return;
}
if (typeof data === 'object') {
- for (let attribute in data) {
+ for (const attribute in data) {
recursiveJSONProcess(data[attribute], level += 1, path + attribute + '/', lookupPath + attribute + '/', id, file, component);
}
return;
}
if (data && translatablePaths.includes(lookupPath)) {
exportTextData.push({
- file: file,
- id: id,
- path: path,
+ file,
+ id,
+ path,
value: data
});
}
@@ -174,7 +174,7 @@ class Translate {
fs.mkdirpSync(outputFolder);
if (this.format === 'json' || this.format === 'raw') {
- const filePath = path.join(outputFolder, `export.json`);
+ const filePath = path.join(outputFolder, 'export.json');
this.log(`Exporting json to ${filePath}`);
fs.writeJSONSync(filePath, exportTextData, { spaces: 2 });
return;
@@ -218,7 +218,7 @@ class Translate {
async import() {
if (this.isTest) {
- this.log(`!TEST IMPORT, not changing data.`);
+ this.log('!TEST IMPORT, not changing data.');
}
// check that a targetLang has been specified
@@ -327,7 +327,7 @@ class Translate {
throw new Error(`Too few columns detected: expected 2, found ${line.length} in ${filename}`);
}
if (line.length !== 2 && !hasWarnedTruncated) {
- this.log(`Truncating, too many columns detected: expected 2, found extra ${line.length-2} in ${filename}`);
+ this.log(`Truncating, too many columns detected: expected 2, found extra ${line.length - 2} in ${filename}`);
hasWarnedTruncated = true;
}
line.length = 2;
@@ -339,8 +339,8 @@ class Translate {
lines.forEach(line => {
const [ file, id, ...path ] = line[0].split('/');
importData.push({
- file: file,
- id: id,
+ file,
+ id,
path: path.filter(Boolean).join('/'),
value: line[1]
});
diff --git a/grunt/helpers/plugins/Plugin.js b/grunt/helpers/plugins/Plugin.js
index ad6670b22..afcc863f9 100644
--- a/grunt/helpers/plugins/Plugin.js
+++ b/grunt/helpers/plugins/Plugin.js
@@ -65,6 +65,10 @@ class Plugin {
return this;
}
+ get isAdaptPlugin() {
+ return Boolean(this.packageJSONFile.firstFileItem.item.keywords?.includes('adapt-plugin'));
+ }
+
/**
* Informs the Schemas API from where to fetch the schemas defined in this
* plugin.
@@ -82,6 +86,7 @@ class Plugin {
*/
get packageJSONLocations() {
return [
+ `${this.sourcePath}package.json`,
`${this.sourcePath}bower.json`
];
}
@@ -106,14 +111,65 @@ class Plugin {
}
const config = this.packageJSONFile.firstFileItem.item;
const configKeys = Object.keys(config);
- const typeKeyName = ['component', 'extension', 'menu', 'theme'];
- const foundType = configKeys.find(key => typeKeyName.includes(key));
+ const typeKeyName = ['core', 'component', 'extension', 'menu', 'theme'];
+ // TODO: bad code
+ const foundType = configKeys.find(key => typeKeyName.includes(key) || (this.packageJSONFile.firstFileItem.item.keywords?.includes('adapt-core') && 'core'));
if (!foundType) {
throw new Error('Unknown plugin type');
}
return foundType;
}
+ /**
+ * Returns the plugin folder name for adapt_framework <=v5
+ * @returns {string}
+ */
+ get legacyFolder() {
+ const typeToFolderMapping = {
+ component: 'components',
+ extension: 'extensions',
+ menu: 'menu',
+ theme: 'theme'
+ };
+ return typeToFolderMapping[this.type];
+ }
+
+ /**
+ * Returns the plugin main path.
+ * @returns {string}
+ */
+ get main() {
+ const config = this.packageJSONFile.firstFileItem.item;
+ return config.main;
+ }
+
+ /**
+ * Returns the client-side module name mappings
+ * @returns {Object}
+ */
+ get compilationMap() {
+ const config = this.packageJSONFile.firstFileItem.item;
+ return config.adapt_framework?.compilationMap || {};
+ }
+
+ /**
+ * Returns the client-side external library name mappings
+ * @returns {Object}
+ */
+ get externalPaths() {
+ const config = this.packageJSONFile.firstFileItem.item;
+ return config.adapt_framework?.externalPaths || {};
+ }
+
+ /**
+ * Returns the client-side external library name redirections
+ * @returns {Object}
+ */
+ get runtimeRedirects() {
+ const config = this.packageJSONFile.firstFileItem.item;
+ return config.adapt_framework?.runtimeRedirects || {};
+ }
+
/**
* @returns {string}
*/
diff --git a/grunt/tasks/handlebars.js b/grunt/tasks/handlebars.js
index 95ed2ac05..9abdf0e9f 100644
--- a/grunt/tasks/handlebars.js
+++ b/grunt/tasks/handlebars.js
@@ -7,43 +7,43 @@
*/
'use strict';
-var chalk = require('chalk');
-var nsdeclare = require('nsdeclare');
+const chalk = require('chalk');
+const nsdeclare = require('nsdeclare');
module.exports = function(grunt) {
- var _ = grunt.util._;
+ const _ = grunt.util._;
// content conversion for templates
- var defaultProcessContent = function(content) {
+ const defaultProcessContent = function(content) {
return content;
};
// AST processing for templates
- var defaultProcessAST = function(ast) {
+ const defaultProcessAST = function(ast) {
return ast;
};
// filename conversion for templates
- var defaultProcessName = function(name) {
+ const defaultProcessName = function(name) {
return name;
};
// filename conversion for partials
- var defaultProcessPartialName = function(filepath) {
- var pieces = _.last(filepath.split('/')).split('.');
- var name = _(pieces).without(_.last(pieces)).join('.'); // strips file extension
+ const defaultProcessPartialName = function(filepath) {
+ const pieces = _.last(filepath.split('/')).split('.');
+ let name = _(pieces).without(_.last(pieces)).join('.'); // strips file extension
if (name.charAt(0) === '_') {
name = name.substr(1, name.length); // strips leading _ character
}
return name;
};
- var extractGlobalNamespace = function(nsDeclarations) {
+ const extractGlobalNamespace = function(nsDeclarations) {
// Extract global namespace from any existing namespace declaration.
// The purpose of this method is too fix an issue with AMD when using namespace as a function where the
// nsInfo.namespace will contains the last namespace, not the global namespace.
- var declarations = _.keys(nsDeclarations);
+ const declarations = _.keys(nsDeclarations);
// no declaration found
if (!declarations.length) {
@@ -57,12 +57,12 @@ module.exports = function(grunt) {
// We only need to take any declaration to extract the global namespace.
// Another option might be find the shortest declaration which is the global one.
// eslint-disable-next-line no-useless-escape
- var matches = declarations[0].match(/(this\[[^\[]+\])/g);
+ const matches = declarations[0].match(/(this\[[^\[]+\])/g);
return matches[0];
};
grunt.registerMultiTask('handlebars', 'Compile handlebars templates and partials.', function() {
- var options = this.options({
+ const options = this.options({
namespace: 'JST',
separator: grunt.util.linefeed + grunt.util.linefeed,
wrapped: true,
@@ -73,43 +73,43 @@ module.exports = function(grunt) {
});
// assign regex for partials directory detection
- var partialsPathRegex = options.partialsPathRegex || /./;
+ const partialsPathRegex = options.partialsPathRegex || /./;
// assign regex for partial detection
- var isPartialRegex = options.partialRegex || /^_/;
+ const isPartialRegex = options.partialRegex || /^_/;
// assign transformation functions
- var processContent = options.processContent || defaultProcessContent;
- var processName = options.processName || defaultProcessName;
- var processPartialName = options.processPartialName || defaultProcessPartialName;
- var processAST = options.processAST || defaultProcessAST;
- var useNamespace = options.namespace !== false;
+ const processContent = options.processContent || defaultProcessContent;
+ const processName = options.processName || defaultProcessName;
+ const processPartialName = options.processPartialName || defaultProcessPartialName;
+ const processAST = options.processAST || defaultProcessAST;
+ const useNamespace = options.namespace !== false;
// assign compiler options
- var compilerOptions = options.compilerOptions || {};
- var filesCount = 0;
+ const compilerOptions = options.compilerOptions || {};
+ let filesCount = 0;
this.files.forEach(function(f) {
- var declarations = [];
- var partials = {};
- var templates = {};
+ const declarations = [];
+ const partials = {};
+ const templates = {};
// template identifying parts
- var ast, compiled, filename;
+ let ast, compiled, filename;
// Namespace info for current template
- var nsInfo;
+ let nsInfo;
// Map of already declared namespace parts
- var nsDeclarations = {};
+ const nsDeclarations = {};
// nsdeclare options when fetching namespace info
- var nsDeclareOptions = {
+ const nsDeclareOptions = {
response: 'details',
declared: nsDeclarations
};
// Just get the namespace info for a given template
- var getNamespaceInfo = _.memoize(function(filepath) {
+ const getNamespaceInfo = _.memoize(function(filepath) {
if (!useNamespace) {
return undefined;
}
@@ -129,9 +129,9 @@ module.exports = function(grunt) {
return true;
})
.forEach(function(filepath) {
- var src = processContent(grunt.file.read(filepath), filepath);
+ const src = processContent(grunt.file.read(filepath), filepath);
- var Handlebars = require('handlebars');
+ const Handlebars = require('handlebars');
try {
// parse the handlebars template into it's AST
ast = processAST(Handlebars.parse(src));
@@ -142,13 +142,13 @@ module.exports = function(grunt) {
compiled = `Handlebars.template(${compiled})`;
}
} catch (e) {
- const title = `Handlebars failed to compile ${filepath}.`
+ const title = `Handlebars failed to compile ${filepath}.`;
e.message = `${title}\n${e.message}`;
console.error(e.toString());
grunt.fail.fatal(title);
}
- var stringifiedFileName;
+ let stringifiedFileName;
// register partial or add template to namespace
if (partialsPathRegex.test(filepath) && isPartialRegex.test(_.last(filepath.split('/')))) {
filename = processPartialName(filepath);
@@ -186,16 +186,16 @@ module.exports = function(grunt) {
}
});
- var output = declarations.concat(_.values(partials), _.values(templates));
+ const output = declarations.concat(_.values(partials), _.values(templates));
if (output.length < 1) {
grunt.log.warn('Destination not written because compiled files were empty.');
} else {
if (useNamespace) {
if (options.node) {
- output.unshift(`Handlebars = glob.Handlebars || require('handlebars');`);
- output.unshift(`var glob = ('undefined' === typeof window) ? global : window,`);
+ output.unshift('Handlebars = glob.Handlebars || require(\'handlebars\');');
+ output.unshift('var glob = (\'undefined\' === typeof window) ? global : window,');
- var nodeExport = `if (typeof exports === 'object' && exports) {`;
+ let nodeExport = 'if (typeof exports === \'object\' && exports) {';
nodeExport += `module.exports = ${nsInfo.namespace};}`;
output.push(nodeExport);
@@ -206,15 +206,15 @@ module.exports = function(grunt) {
if (options.amd) {
// Wrap the file in an AMD define fn.
if (typeof options.amd === 'boolean') {
- output.unshift(`define(['handlebars'], function(Handlebars) {`);
+ output.unshift('define([\'handlebars\'], function(Handlebars) {');
} else if (typeof options.amd === 'string') {
output.unshift(`define(['${options.amd}'], function(Handlebars) {`);
} else if (typeof options.amd === 'function') {
output.unshift(`define(['${options.amd(filename, ast, compiled)}'], function(Handlebars) {`);
} else if (Array.isArray(options.amd)) {
// convert options.amd to a string of dependencies for require([...])
- var amdString = '';
- for (var i = 0; i < options.amd.length; i++) {
+ let amdString = '';
+ for (let i = 0; i < options.amd.length; i++) {
if (i !== 0) {
amdString += ', ';
}
diff --git a/grunt/tasks/javascript.js b/grunt/tasks/javascript.js
index fa503eedf..99c76484b 100644
--- a/grunt/tasks/javascript.js
+++ b/grunt/tasks/javascript.js
@@ -12,7 +12,12 @@ module.exports = function(grunt) {
const fs = require('fs-extra');
const rollup = require('rollup');
const { babel } = require('@rollup/plugin-babel');
+ const { nodeResolve } = require('@rollup/plugin-node-resolve');
+ const commonjs = require('@rollup/plugin-commonjs');
+ const replace = require('@rollup/plugin-replace');
const terser = require('@rollup/plugin-terser');
+ const resolve = require('resolve');
+ const MagicString = require('magic-string');
const { deflate, unzip, constants } = require('zlib');
const cwd = process.cwd().replace(convertSlashes, '/') + '/';
@@ -120,6 +125,13 @@ module.exports = function(grunt) {
}
};
+ const resolvedNodeModules = {};
+ const findNodeModule = (cwd, baseUrl, moduleId) => {
+ if (resolvedNodeModules[moduleId]) return resolvedNodeModules[moduleId];
+ resolvedNodeModules[moduleId] = resolve.sync(moduleId, { basedir: path.resolve(cwd, baseUrl) });
+ return resolvedNodeModules[moduleId];
+ };
+
grunt.registerMultiTask('javascript', 'Compile JavaScript files', async function() {
const Helpers = require('../helpers')(grunt);
const buildConfig = Helpers.generateConfigData();
@@ -131,46 +143,102 @@ module.exports = function(grunt) {
const isSourceMapped = Boolean(options.generateSourceMaps);
const basePath = path.resolve(cwd + '/' + options.baseUrl).replace(convertSlashes, '/') + '/';
const cachePath = buildConfig.cachepath ?? cacheManager.cachePath(cwd, options.out);
+
+ const framework = Helpers.getFramework();
+ const pluginsObject = framework.getPlugins();
+ const pluginPackageJSONs = pluginsObject.getAllPackageJSONFileItems().map(item => item.item);
+ const pluginMappings = pluginPackageJSONs.reduce((mappings, packageJSON) => {
+ if (!packageJSON.adapt_framework) return mappings;
+ mappings.map = Object.assign({}, mappings.map, packageJSON.adapt_framework.map);
+ mappings.paths = Object.assign({}, mappings.paths, packageJSON.adapt_framework.paths);
+ return mappings;
+ }, {});
+
try {
await restoreCache(cachePath, basePath);
await cacheManager.clean();
- const pluginsPath = path.resolve(cwd, options.pluginsPath).replace(convertSlashes, '/');
-
- // Make src/plugins.js to attach the plugins dynamically
- if (!fs.existsSync(pluginsPath)) {
- fs.writeFileSync(pluginsPath, '');
- }
// Collect all plugin entry points for injection
- const pluginPaths = [];
- for (let i = 0, l = options.plugins.length; i < l; i++) {
- const src = options.plugins[i];
- grunt.file.expand({
- filter: options.pluginsFilter
- }, src).forEach(function(bowerJSONPath) {
- if (bowerJSONPath === undefined) return;
- const pluginPath = path.dirname(bowerJSONPath);
- const bowerJSON = grunt.file.readJSON(bowerJSONPath);
- const requireJSRootPath = pluginPath.substr(options.baseUrl.length);
- const requireJSMainPath = path.join(requireJSRootPath, bowerJSON.main);
- const ext = path.extname(requireJSMainPath);
- const requireJSMainPathNoExt = requireJSMainPath.slice(0, -ext.length).replace(convertSlashes, '/');
- pluginPaths.push(requireJSMainPathNoExt);
- });
- }
+ const pluginPaths = options.pluginsOrder(pluginsObject.plugins.map(plugin => {
+ if (plugin.name === 'adapt-contrib-core') return null;
+ const requireJSRootPath = plugin.sourcePath.substr(options.baseUrl.length);
+ const requireJSMainPath = path.join(requireJSRootPath, plugin.main);
+ const ext = path.extname(requireJSMainPath);
+ const requireJSMainPathNoExt = requireJSMainPath.slice(0, -ext.length).replace(convertSlashes, '/');
+ return requireJSMainPathNoExt;
+ }).filter(Boolean));
// Collect react templates
const reactTemplatePaths = [];
options.reactTemplates.forEach(pattern => {
grunt.file.expand({
- filter: options.pluginsFilter
- }, pattern).forEach(templatePath => reactTemplatePaths.push(templatePath.replace(convertSlashes, '/')));
+ filter: options.pluginsFilter,
+ order: options.pluginsOrder
+ }, pattern).forEach(templatePath => {
+ const requireJSRootPath = templatePath.substr(options.baseUrl.length);
+ return reactTemplatePaths.push(requireJSRootPath.replace(convertSlashes, '/'));
+ });
});
+ const isProduction = false;
+ // These will be overridden/added to in the package.json of any plugin
+ // they are here for backward compatibility only
+ const v5toV6DefaultMappings = {
+ externalMap: {
+ '.*/libraries/(?!mediaelement-fullscreen-hook)+': 'libraries/'
+ },
+ map: {
+ 'components/': '',
+ 'extensions/': '',
+ 'menu/': '',
+ 'theme/': '',
+ 'core/': 'adapt-contrib-core/',
+ coreJS: 'adapt-contrib-core/js',
+ coreViews: 'adapt-contrib-core/js/views',
+ coreModels: 'adapt-contrib-core/js/models',
+ coreCollections: 'adapt-contrib-core/js/collections',
+ coreHelpers: 'adapt-contrib-core/js/helpers',
+ // This library from the media component has a circular reference to core/js/adapt, it should be loaded after Adapt
+ // It needs to be moved from the libraries folder to the js folder
+ 'libraries/mediaelement-fullscreen-hook': '../libraries/mediaelement-fullscreen-hook'
+ },
+ paths: {
+ jquery: 'libraries/jquery.min',
+ underscore: 'libraries/underscore.min',
+ 'underscore.results': 'libraries/underscore.results',
+ backbone: 'libraries/backbone.min',
+ 'backbone.controller': 'libraries/backbone.controller',
+ 'backbone.controller.results': 'libraries/backbone.controller.results',
+ 'backbone.es6': 'libraries/backbone.es6',
+ handlebars: 'libraries/handlebars.min',
+ velocity: 'libraries/velocity.min',
+ imageReady: 'libraries/imageReady',
+ inview: 'libraries/inview',
+ a11y: 'empty:',
+ plugins: 'empty:',
+ 'libraries/': 'empty:',
+ 'regenerator-runtime': 'libraries/regenerator-runtime.min',
+ 'core-js': 'libraries/core-js.min',
+ scrollTo: 'libraries/scrollTo.min',
+ bowser: 'libraries/bowser',
+ enum: 'libraries/enum',
+ 'core/js/libraries/bowser': 'empty:',
+ 'coreJS/libraries/bowser': 'empty:',
+ 'object.assign': 'empty:',
+ jqueryMobile: 'libraries/jquery.mobile.custom.min',
+ react: isProduction ? 'libraries/react.production.min' : 'libraries/react.development',
+ 'react-dom': isProduction ? 'libraries/react-dom.production.min' : 'libraries/react-dom.development',
+ 'html-react-parser': 'libraries/html-react-parser.min',
+ semver: 'libraries/semver'
+ }
+ };
+
// Process remapping and external model configurations
- const mapParts = Object.keys(options.map);
- const externalParts = Object.keys(options.external);
- const externalMap = options.externalMap;
+ const mappings = Object.assign(v5toV6DefaultMappings.map, pluginMappings.map);
+ const mapParts = Object.keys(mappings);
+ const externals = Object.assign(v5toV6DefaultMappings.paths, pluginMappings.paths);
+ const externalParts = Object.keys(externals);
+ const externalMap = (v5toV6DefaultMappings.externalMap);
const findFile = function(filename) {
filename = filename.replace(convertSlashes, '/');
@@ -182,8 +250,6 @@ module.exports = function(grunt) {
return filename;
};
- const umdImports = options.umdImports.map(filename => findFile(path.resolve(basePath, filename)));
-
// Rework modules names and inject plugins
const adaptLoader = function() {
return {
@@ -196,13 +262,32 @@ module.exports = function(grunt) {
// Ignore as injected rollup module
return null;
}
+ if (moduleId.startsWith(basePath)) {
+ moduleId = moduleId.substr(basePath.length);
+ }
const mapPart = mapParts.find(part => moduleId.startsWith(part));
if (mapPart) {
// Remap module, usually coreJS/adapt to core/js/adapt etc
- moduleId = moduleId.replace(mapPart, options.map[mapPart]);
+ moduleId = moduleId.replace(mapPart, mappings[mapPart]);
}
// Remap ../libraries/ or core/js/libraries/ to libraries/
moduleId = Object.entries(externalMap).reduce((moduleId, [ match, replaceWith ]) => moduleId.replace((new RegExp(match, 'g')), replaceWith), moduleId);
+ const externalPart = externalParts.find(part => moduleId.startsWith(part));
+ const isExternal = Boolean(externals[externalPart]);
+ const isNodeModule = Boolean(externals[externalPart] === 'node_modules:');
+ if (isExternal && !isNodeModule) {
+ // External module as defined in paths
+ return {
+ id: moduleId,
+ external: true
+ };
+ }
+ try {
+ // Resolve node modules
+ if (!moduleId.includes('adapt-') && findNodeModule(cwd, options.baseUrl, moduleId)) {
+ return null;
+ }
+ } catch (err) {}
const isRelative = (moduleId[0] === '.');
if (isRelative) {
if (!parentId) {
@@ -220,15 +305,6 @@ module.exports = function(grunt) {
external: false
};
}
- const externalPart = externalParts.find(part => moduleId.startsWith(part));
- const isEmpty = (options.external[externalPart] === 'empty:');
- if (isEmpty) {
- // External module as is defined as 'empty:', libraries/ bower handlebars etc
- return {
- id: moduleId,
- external: true
- };
- }
const isES6Import = !fs.existsSync(moduleId);
if (isES6Import) {
// ES6 imports start inside ./src so need correcting
@@ -259,17 +335,35 @@ module.exports = function(grunt) {
if (isRollupHelper) {
return null;
}
- const isPlugins = (moduleId.includes('/' + options.pluginsModule + '.js'));
- if (!isPlugins) {
+ const isStart = (moduleId.includes('/' + options.baseUrl + options.name));
+ if (!isStart) {
return null;
}
- // Dynamically construct plugins.js with plugin dependencies
- code = `define([${pluginPaths.map(filename => {
- return `"${filename}"`;
+
+ const magicString = new MagicString(code);
+ const matches = [...String(code).matchAll(/import .*;/g)];
+ const last = matches[matches.length - 1];
+ const end = last.index + last[0].length;
+
+ const original = code.substr(0, end);
+ const newPart = `\n${pluginPaths.map(filename => {
+ return `import '${filename}';\n`;
}).concat(reactTemplatePaths.map(filename => {
- return `"${filename}"`;
- })).join(',')}], function() {});`;
- return code;
+ return `import '${filename}';\n`;
+ })).join('')}`;
+
+ magicString.remove(0, end);
+ magicString.prepend(original + newPart);
+
+ return {
+ code: magicString.toString(),
+ map: isSourceMapped
+ ? magicString.generateMap({
+ filename: moduleId,
+ includeContent: true
+ })
+ : false
+ };
}
};
@@ -300,15 +394,25 @@ module.exports = function(grunt) {
plugins: [
adaptLoader({}),
adaptInjectPlugins({}),
+ replace({
+ 'process.env.NODE_ENV': isSourceMapped ? JSON.stringify('development') : JSON.stringify('production'),
+ preventAssignment: true
+ }),
+ commonjs({
+ exclude: ['**/node_modules/adapt-*/**'],
+ sourceMap: isSourceMapped
+ }),
+ nodeResolve({
+ browser: true,
+ preferBuiltins: false
+ }),
babel({
babelHelpers: 'bundled',
extensions,
minified: false,
compact: false,
comments: false,
- exclude: [
- '**/node_modules/**'
- ],
+ exclude: [ '**/node_modules/core-js/**' ],
presets: [
[
'@babel/preset-react',
@@ -319,7 +423,7 @@ module.exports = function(grunt) {
[
'@babel/preset-env',
{
- useBuiltIns: 'entry',
+ useBuiltIns: 'usage',
corejs: 3,
exclude: [
// Breaks lockingModel.js, set function vs set variable
@@ -339,8 +443,11 @@ module.exports = function(grunt) {
ignoreNestedRequires: true,
defineFunctionName: '__AMD',
defineModuleId: (moduleId) => moduleId.replace(convertSlashes, '/').replace(basePath, '').replace('.js', ''),
+ includes: [
+ '**/node_modules/adapt-*/**'
+ ],
excludes: [
- '**/templates/**/*.jsx'
+ '**/node_modules/adapt-*/templates/**/*.jsx'
]
}
],
@@ -348,7 +455,7 @@ module.exports = function(grunt) {
'transform-react-templates',
{
includes: [
- '**/templates/**/*.jsx'
+ '**/node_modules/adapt-*/templates/**/*.jsx'
],
importRegisterFunctionFromModule: path.resolve(basePath, 'core/js/reactHelpers.js').replace(convertSlashes, '/'),
registerFunctionName: 'register',
@@ -357,29 +464,72 @@ module.exports = function(grunt) {
]
]
})
- ]
+ ].filter(Boolean)
};
- const umdImport = () => {
- return umdImports.map(filename => {
- let code = fs.readFileSync(filename).toString();
- code = code
- .replace('require("object-assign")', 'Object.assign')
- .replace('define.amd', 'define.noop');
- return code;
- }).join('\n');
- };
+ const clientSidePaths = Object.entries(externals)
+ .filter(([, value]) => {
+ // any forced node_module, at odds with the compatibility defaults
+ const isNodeModule = (value === 'node_modules:');
+ // general prefixes libraries/
+ const isEmpty = (value === 'empty:');
+ return (!isNodeModule && !isEmpty);
+ })
+ .reduce((output, [key, value]) => {
+ output[key] = value;
+ return output;
+ }, {});
const outputOptions = {
file: options.out,
format: 'amd',
+ interop: false,
+ banner: `
+requirejs.config({
+ map: {
+ '*': ${JSON.stringify(mappings, null, 2)}
+ },
+ paths: ${JSON.stringify(clientSidePaths, null, 2)}
+});
+define('plugins', () => true);
+(function requireJSMapExtension() {
+ // Extend requirejs map with direct string replacement
+ const requireJSMapConfig = requirejs.s.contexts._.config.map['*'];
+ const remapConfigs = Object.entries(requireJSMapConfig).map(([ find, replaceWith ]) => {
+ return {
+ find: new RegExp("^" + find),
+ replaceWith
+ };
+ });
+ function stringReplaceModuleIds(original, ...args) {
+ let [ names ] = args;
+ const isArray = Array.isArray(names);
+ if (!isArray) names = [names];
+ names = names.map(name => {
+ remapConfigs.forEach(({ find, replaceWith }) => {
+ name = name.replace(find, replaceWith);
+ });
+ return name;
+ });
+ args[0] = isArray ? names : names[0];
+ return original.apply(this, args);
+ }
+ function monkeyPunch(context, functionName, trap) {
+ const original = context[functionName];
+ context[functionName] = function(...args) {
+ args.unshift(original);
+ return trap.apply(context, args);
+ };
+ }
+ monkeyPunch(requirejs.s.contexts._, 'require', stringReplaceModuleIds);
+})();
+`,
plugins: [
!isSourceMapped && terser({
mangle: false,
compress: false
})
].filter(Boolean),
- intro: umdImport(),
footer: `// Allow ES export default to be exported as amd modules
window.__AMD = function(id, value) {
window.define(id, function() { return value; }); // define for external use
@@ -397,7 +547,8 @@ window.__AMD = function(id, value) {
strict: isStrictMode
};
- checkCache([pluginsPath]);
+ const mainPath = path.resolve(cwd, options.baseUrl, options.name + '.js').replace(convertSlashes, '/');
+ checkCache([mainPath]);
inputOptions.cache = cache;
const bundle = await rollup.rollup(inputOptions);
await saveCache(cachePath, basePath, bundle.cache);
diff --git a/grunt/tasks/less.js b/grunt/tasks/less.js
index 82bdc220f..ec74e59a4 100644
--- a/grunt/tasks/less.js
+++ b/grunt/tasks/less.js
@@ -1,46 +1,39 @@
module.exports = function(grunt) {
- var convertSlashes = /\\/g;
-
grunt.registerMultiTask('less', 'Compile LESS files to CSS', function() {
- var less = require('less');
- var _ = require('underscore');
- var path = require('path');
- var Visitors = require('./less/visitors');
- var done = this.async();
- var options = this.options({});
-
- var rootPath = path.join(path.resolve(options.baseUrl), '../')
- .replace(convertSlashes, '/');
- var cwd = process.cwd();
+ const less = require('less');
+ const _ = require('underscore');
+ const path = require('path');
+ const Visitors = require('./less/visitors');
+ const done = this.async();
+ const options = this.options({});
- var imports = '';
- var src = '';
+ let imports = '';
if (options.src && options.config) {
- var screenSize = {
- 'small': 520,
- 'medium': 760,
- 'large': 900
+ let screenSize = {
+ small: 520,
+ medium: 760,
+ large: 900
};
try {
- var configjson = JSON.parse(grunt.file.read(options.config)
+ const configjson = JSON.parse(grunt.file.read(options.config)
.toString());
screenSize = configjson.screenSize || screenSize;
} catch (e) {}
- var screensizeEmThreshold = 300;
- var baseFontSize = 16;
+ const screensizeEmThreshold = 300;
+ const baseFontSize = 16;
// Check to see if the screen size value is larger than the em threshold
// If value is larger than em threshold, convert value (assumed px) to ems
// Otherwise assume value is in ems
- var largeEmBreakpoint = screenSize.large > screensizeEmThreshold ?
+ const largeEmBreakpoint = screenSize.large > screensizeEmThreshold ?
screenSize.large / baseFontSize :
screenSize.large;
- var mediumEmBreakpoint = screenSize.medium > screensizeEmThreshold ?
+ const mediumEmBreakpoint = screenSize.medium > screensizeEmThreshold ?
screenSize.medium / baseFontSize :
screenSize.medium;
- var smallEmBreakpoint = screenSize.small > screensizeEmThreshold ?
+ const smallEmBreakpoint = screenSize.small > screensizeEmThreshold ?
screenSize.small / baseFontSize :
screenSize.small;
@@ -51,47 +44,43 @@ module.exports = function(grunt) {
if (options.mandatory) {
for (let i = 0, l = options.mandatory.length; i < l; i++) {
- src = path.join(cwd, options.mandatory[i]);
grunt.file.expand({
follow: true,
order: options.order
- }, src)
+ }, options.mandatory[i])
.forEach(function(lessPath) {
lessPath = path.normalize(lessPath);
- var trimmed = lessPath.substr(rootPath.length);
- imports += "@import '" + trimmed + "';\n";
+ imports += "@import '" + lessPath + "';\n";
});
}
}
if (options.src) {
for (let i = 0, l = options.src.length; i < l; i++) {
- src = path.join(cwd, options.src[i]);
grunt.file.expand({
follow: true,
filter: options.filter,
order: options.order
- }, src)
+ }, options.src[i])
.forEach(function(lessPath) {
lessPath = path.normalize(lessPath);
- var trimmed = lessPath.substr(rootPath.length);
- imports += "@import '" + trimmed + "';\n";
+ imports += "@import '" + lessPath + "';\n";
});
}
}
- var sourcemaps;
+ let sourcemaps;
if (options.sourcemaps) {
sourcemaps = {
- 'sourceMap': {
- 'sourceMapFileInline': false,
- 'outputSourceFiles': true,
- 'sourceMapBasepath': 'src',
- 'sourceMapURL': options.mapFilename
+ sourceMap: {
+ sourceMapFileInline: false,
+ outputSourceFiles: true,
+ sourceMapBasepath: 'src/node_modules',
+ sourceMapURL: options.mapFilename
}
};
} else {
- var sourceMapPath = path.join(options.dest, options.mapFilename);
+ const sourceMapPath = path.join(options.dest, options.mapFilename);
if (grunt.file.exists(sourceMapPath)) {
grunt.file.delete(sourceMapPath, {
force: true
@@ -104,11 +93,11 @@ module.exports = function(grunt) {
}
}
- var visitors = new Visitors(options);
+ const visitors = new Visitors(options);
- var lessOptions = _.extend({
- 'compress': options.compress,
- 'plugins': [
+ const lessOptions = _.extend({
+ compress: options.compress,
+ plugins: [
visitors
]
}, sourcemaps);
diff --git a/grunt/tasks/scripts.js b/grunt/tasks/scripts.js
index 69eafbcd9..a6bdc4398 100644
--- a/grunt/tasks/scripts.js
+++ b/grunt/tasks/scripts.js
@@ -4,41 +4,42 @@ module.exports = function(grunt) {
if (!mode) return;
- var path = require('path');
- var fs = require('fs');
- var options = this.options({});
- var chalk = require('chalk');
- var async = require('async');
- var taskCallback = this.async();
+ const path = require('path');
+ const fs = require('fs');
+ const options = this.options({});
+ const chalk = require('chalk');
+ const async = require('async');
+ const taskCallback = this.async();
if (options.plugins) {
- var paths = [];
- for (var i = 0, l = options.plugins.length; i < l; i++) {
- var src = options.plugins[i];
+ let paths = [];
+ for (let i = 0, l = options.plugins.length; i < l; i++) {
+ const src = options.plugins[i];
paths = paths.concat(grunt.file.expand({
- filter: options.pluginsFilter
+ filter: options.pluginsFilter,
+ order: options.pluginsOrder
}, src));
}
async.each(paths, function(bowerJSONPath, done) {
if (bowerJSONPath === undefined) return done();
- var bowerJSON = grunt.file.readJSON(bowerJSONPath);
+ const bowerJSON = grunt.file.readJSON(bowerJSONPath);
if (!bowerJSON.scripts) return done();
- var plugindir = path.dirname(bowerJSONPath);
- var script = bowerJSON.scripts[mode];
+ const plugindir = path.dirname(bowerJSONPath);
+ const script = bowerJSON.scripts[mode];
if (!script) return done();
try {
- var buildModule = require(path.join(process.cwd(), plugindir, script));
+ const buildModule = require(path.join(process.cwd(), plugindir, script));
buildModule(fs, path, grunt.log.writeln, {
sourcedir: options.sourcedir,
outputdir: options.outputdir,
- plugindir: plugindir
+ plugindir
}, done);
} catch (err) {
grunt.log.writeln(chalk.red(err));
diff --git a/grunt/tasks/translate.js b/grunt/tasks/translate.js
index e2f7c73bb..afbc18959 100644
--- a/grunt/tasks/translate.js
+++ b/grunt/tasks/translate.js
@@ -7,7 +7,7 @@ module.exports = function(grunt) {
const next = this.async();
const framework = Helpers.getFramework();
- grunt.log.ok(`Using ${framework.useOutputData ? framework.outputPath : framework.sourcePath} folder for course data...`);
+ grunt.log.ok(`Using ${framework.outputPath} folder for course data...`);
const translate = framework.getTranslate({
masterLang: grunt.option('masterLang') || 'en',
@@ -31,16 +31,16 @@ module.exports = function(grunt) {
translate.import().then(next, err => {
switch (err.number) {
case 10001: // Target language option is missing
- grunt.log.error(err + `\nPlease add --targetLang=`);
+ grunt.log.error(err + '\nPlease add --targetLang=');
break;
case 10002: // Import source folder does not exist.
- grunt.log.error(err + `\nPlease create this folder in the languagefiles directory.`);
+ grunt.log.error(err + '\nPlease create this folder in the languagefiles directory.');
break;
case 10003: // Import destination folder already exists.
- grunt.log.error(err + `\nTo replace the content in this folder, please add a --replace flag to the grunt task.`);
+ grunt.log.error(err + '\nTo replace the content in this folder, please add a --replace flag to the grunt task.');
break;
case 10014: // Could not detect delimiter
- grunt.log.error(err + `\nTo specify a delimiter, please add a --csvDelimiter=',' flag to the grunt task. Only , ; | tab and space are supported.`);
+ grunt.log.error(err + '\nTo specify a delimiter, please add a --csvDelimiter=\',\' flag to the grunt task. Only , ; | tab and space are supported.');
break;
default:
grunt.log.error(err);
diff --git a/jsconfig.json b/jsconfig.json
index 0ccb50e26..547a4376c 100644
--- a/jsconfig.json
+++ b/jsconfig.json
@@ -1,15 +1,13 @@
{
"include": [
- "src/core/js/**/*.js",
- "src/*/*/js/**/*.js"
+ "src/node_modules/adapt-*/js/**/*.js",
+ "src/node_modules/adapt-*/templates/**/*.jsx",
],
"compilerOptions": {
"module": "amd",
- "target": "es2015",
+ "target": "es2020",
"baseUrl": "src/"
},
"exclude": [
- "node_modules",
- "**/node_modules/*"
]
}
diff --git a/package-lock.json b/package-lock.json
index 6614ea976..d706f2842 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,20 +1,24 @@
{
"name": "adapt_framework",
- "version": "5.24.8",
+ "version": "5.27.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "adapt_framework",
- "version": "5.24.8",
+ "version": "5.27.0",
"hasInstallScript": true,
"license": "GPL-3.0",
"dependencies": {
"@babel/core": "^7.20.12",
+ "@babel/plugin-transform-modules-commonjs": "^7.21.2",
"@babel/plugin-transform-react-jsx": "^7.20.13",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@rollup/plugin-babel": "^6.0.3",
+ "@rollup/plugin-commonjs": "^24.0.1",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@types/backbone": "^1.4.14",
"@types/jquery": "^3.5.11",
@@ -46,7 +50,9 @@
"less": "^3.9.0",
"load-grunt-config": "^4.0.1",
"lodash": "^4.17.19",
+ "magic-string": "^0.30.0",
"nsdeclare": "^0.1.0",
+ "resolve": "^1.22.1",
"rollup": "^2.79.1",
"time-grunt": "^2.0.0",
"underscore": "^1.13.1",
@@ -141,12 +147,13 @@
}
},
"node_modules/@babel/generator": {
- "version": "7.20.14",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz",
- "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==",
+ "version": "7.21.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz",
+ "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==",
"dependencies": {
- "@babel/types": "^7.20.7",
+ "@babel/types": "^7.21.0",
"@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"engines": {
@@ -291,12 +298,12 @@
}
},
"node_modules/@babel/helper-function-name": {
- "version": "7.19.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
- "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+ "version": "7.21.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
+ "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
"dependencies": {
- "@babel/template": "^7.18.10",
- "@babel/types": "^7.19.0"
+ "@babel/template": "^7.20.7",
+ "@babel/types": "^7.21.0"
},
"engines": {
"node": ">=6.9.0"
@@ -336,9 +343,9 @@
}
},
"node_modules/@babel/helper-module-transforms": {
- "version": "7.20.11",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz",
- "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
+ "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
"dependencies": {
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-module-imports": "^7.18.6",
@@ -346,8 +353,8 @@
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1",
"@babel/template": "^7.20.7",
- "@babel/traverse": "^7.20.10",
- "@babel/types": "^7.20.7"
+ "@babel/traverse": "^7.21.2",
+ "@babel/types": "^7.21.2"
},
"engines": {
"node": ">=6.9.0"
@@ -502,9 +509,9 @@
}
},
"node_modules/@babel/parser": {
- "version": "7.20.13",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
- "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz",
+ "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==",
"bin": {
"parser": "bin/babel-parser.js"
},
@@ -1193,13 +1200,13 @@
}
},
"node_modules/@babel/plugin-transform-modules-commonjs": {
- "version": "7.19.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz",
- "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz",
+ "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==",
"dependencies": {
- "@babel/helper-module-transforms": "^7.19.6",
- "@babel/helper-plugin-utils": "^7.19.0",
- "@babel/helper-simple-access": "^7.19.4"
+ "@babel/helper-module-transforms": "^7.21.2",
+ "@babel/helper-plugin-utils": "^7.20.2",
+ "@babel/helper-simple-access": "^7.20.2"
},
"engines": {
"node": ">=6.9.0"
@@ -1649,18 +1656,18 @@
}
},
"node_modules/@babel/traverse": {
- "version": "7.20.13",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
- "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz",
+ "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==",
"dependencies": {
"@babel/code-frame": "^7.18.6",
- "@babel/generator": "^7.20.7",
+ "@babel/generator": "^7.21.1",
"@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-function-name": "^7.19.0",
+ "@babel/helper-function-name": "^7.21.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/parser": "^7.20.13",
- "@babel/types": "^7.20.7",
+ "@babel/parser": "^7.21.2",
+ "@babel/types": "^7.21.2",
"debug": "^4.1.0",
"globals": "^11.1.0"
},
@@ -1669,9 +1676,9 @@
}
},
"node_modules/@babel/types": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
- "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz",
+ "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==",
"dependencies": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
@@ -2088,6 +2095,133 @@
}
}
},
+ "node_modules/@rollup/plugin-commonjs": {
+ "version": "24.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
+ "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.2",
+ "glob": "^8.0.3",
+ "is-reference": "1.2.1",
+ "magic-string": "^0.27.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.68.0||^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs/node_modules/magic-string": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
+ "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@rollup/plugin-commonjs/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@rollup/plugin-node-resolve": {
+ "version": "15.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",
+ "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "@types/resolve": "1.20.2",
+ "deepmerge": "^4.2.2",
+ "is-builtin-module": "^3.2.0",
+ "is-module": "^1.0.0",
+ "resolve": "^1.22.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^2.78.0||^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-replace": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
+ "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==",
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "magic-string": "^0.27.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/plugin-replace/node_modules/magic-string": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
+ "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/@rollup/plugin-terser": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.0.tgz",
@@ -2439,6 +2573,11 @@
"integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==",
"optional": true
},
+ "node_modules/@types/resolve": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
+ },
"node_modules/@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
@@ -3710,6 +3849,17 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
+ "node_modules/builtin-modules": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+ "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw==",
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/builtins": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
@@ -4029,6 +4179,11 @@
"node": ">= 6"
}
},
+ "node_modules/commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
+ },
"node_modules/compare-func": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
@@ -4710,6 +4865,14 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "node_modules/deepmerge": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+ "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -8022,6 +8185,20 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-builtin-module": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+ "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
+ "dependencies": {
+ "builtin-modules": "^3.3.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -8108,6 +8285,11 @@
"node": ">=6"
}
},
+ "node_modules/is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
+ },
"node_modules/is-natural-number": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
@@ -8220,6 +8402,14 @@
"node": ">=8"
}
},
+ "node_modules/is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "dependencies": {
+ "@types/estree": "*"
+ }
+ },
"node_modules/is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -9386,6 +9576,17 @@
"node": ">=10"
}
},
+ "node_modules/magic-string": {
+ "version": "0.30.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
+ "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
@@ -16171,12 +16372,13 @@
}
},
"@babel/generator": {
- "version": "7.20.14",
- "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.14.tgz",
- "integrity": "sha512-AEmuXHdcD3A52HHXxaTmYlb8q/xMEhoRP67B3T4Oq7lbmSoqroMZzjnGj3+i1io3pdnF8iBYVu4Ilj+c4hBxYg==",
+ "version": "7.21.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz",
+ "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==",
"requires": {
- "@babel/types": "^7.20.7",
+ "@babel/types": "^7.21.0",
"@jridgewell/gen-mapping": "^0.3.2",
+ "@jridgewell/trace-mapping": "^0.3.17",
"jsesc": "^2.5.1"
},
"dependencies": {
@@ -16286,12 +16488,12 @@
}
},
"@babel/helper-function-name": {
- "version": "7.19.0",
- "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
- "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+ "version": "7.21.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz",
+ "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==",
"requires": {
- "@babel/template": "^7.18.10",
- "@babel/types": "^7.19.0"
+ "@babel/template": "^7.20.7",
+ "@babel/types": "^7.21.0"
}
},
"@babel/helper-hoist-variables": {
@@ -16319,9 +16521,9 @@
}
},
"@babel/helper-module-transforms": {
- "version": "7.20.11",
- "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.11.tgz",
- "integrity": "sha512-uRy78kN4psmji1s2QtbtcCSaj/LILFDp0f/ymhpQH5QY3nljUZCaNWz9X1dEj/8MBdBEFECs7yRhKn8i7NjZgg==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz",
+ "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==",
"requires": {
"@babel/helper-environment-visitor": "^7.18.9",
"@babel/helper-module-imports": "^7.18.6",
@@ -16329,8 +16531,8 @@
"@babel/helper-split-export-declaration": "^7.18.6",
"@babel/helper-validator-identifier": "^7.19.1",
"@babel/template": "^7.20.7",
- "@babel/traverse": "^7.20.10",
- "@babel/types": "^7.20.7"
+ "@babel/traverse": "^7.21.2",
+ "@babel/types": "^7.21.2"
}
},
"@babel/helper-optimise-call-expression": {
@@ -16440,9 +16642,9 @@
}
},
"@babel/parser": {
- "version": "7.20.13",
- "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.13.tgz",
- "integrity": "sha512-gFDLKMfpiXCsjt4za2JA9oTMn70CeseCehb11kRZgvd7+F67Hih3OHOK24cRrWECJ/ljfPGac6ygXAs/C8kIvw=="
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz",
+ "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ=="
},
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": {
"version": "7.18.6",
@@ -16870,13 +17072,13 @@
}
},
"@babel/plugin-transform-modules-commonjs": {
- "version": "7.19.6",
- "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.19.6.tgz",
- "integrity": "sha512-8PIa1ym4XRTKuSsOUXqDG0YaOlEuTVvHMe5JCfgBMOtHvJKw/4NGovEGN33viISshG/rZNVrACiBmPQLvWN8xQ==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.21.2.tgz",
+ "integrity": "sha512-Cln+Yy04Gxua7iPdj6nOV96smLGjpElir5YwzF0LBPKoPlLDNJePNlrGGaybAJkd0zKRnOVXOgizSqPYMNYkzA==",
"requires": {
- "@babel/helper-module-transforms": "^7.19.6",
- "@babel/helper-plugin-utils": "^7.19.0",
- "@babel/helper-simple-access": "^7.19.4"
+ "@babel/helper-module-transforms": "^7.21.2",
+ "@babel/helper-plugin-utils": "^7.20.2",
+ "@babel/helper-simple-access": "^7.20.2"
}
},
"@babel/plugin-transform-modules-systemjs": {
@@ -17179,26 +17381,26 @@
}
},
"@babel/traverse": {
- "version": "7.20.13",
- "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.13.tgz",
- "integrity": "sha512-kMJXfF0T6DIS9E8cgdLCSAL+cuCK+YEZHWiLK0SXpTo8YRj5lpJu3CDNKiIBCne4m9hhTIqUg6SYTAI39tAiVQ==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz",
+ "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==",
"requires": {
"@babel/code-frame": "^7.18.6",
- "@babel/generator": "^7.20.7",
+ "@babel/generator": "^7.21.1",
"@babel/helper-environment-visitor": "^7.18.9",
- "@babel/helper-function-name": "^7.19.0",
+ "@babel/helper-function-name": "^7.21.0",
"@babel/helper-hoist-variables": "^7.18.6",
"@babel/helper-split-export-declaration": "^7.18.6",
- "@babel/parser": "^7.20.13",
- "@babel/types": "^7.20.7",
+ "@babel/parser": "^7.21.2",
+ "@babel/types": "^7.21.2",
"debug": "^4.1.0",
"globals": "^11.1.0"
}
},
"@babel/types": {
- "version": "7.20.7",
- "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.7.tgz",
- "integrity": "sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg==",
+ "version": "7.21.2",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz",
+ "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==",
"requires": {
"@babel/helper-string-parser": "^7.19.4",
"@babel/helper-validator-identifier": "^7.19.1",
@@ -17509,6 +17711,89 @@
"@rollup/pluginutils": "^5.0.1"
}
},
+ "@rollup/plugin-commonjs": {
+ "version": "24.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-commonjs/-/plugin-commonjs-24.0.1.tgz",
+ "integrity": "sha512-15LsiWRZk4eOGqvrJyu3z3DaBu5BhXIMeWnijSRvd8irrrg9SHpQ1pH+BUK4H6Z9wL9yOxZJMTLU+Au86XHxow==",
+ "requires": {
+ "@rollup/pluginutils": "^5.0.1",
+ "commondir": "^1.0.1",
+ "estree-walker": "^2.0.2",
+ "glob": "^8.0.3",
+ "is-reference": "1.2.1",
+ "magic-string": "^0.27.0"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ }
+ },
+ "magic-string": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
+ "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "requires": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ }
+ },
+ "minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ }
+ }
+ },
+ "@rollup/plugin-node-resolve": {
+ "version": "15.0.1",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-15.0.1.tgz",
+ "integrity": "sha512-ReY88T7JhJjeRVbfCyNj+NXAG3IIsVMsX9b5/9jC98dRP8/yxlZdz7mHZbHk5zHr24wZZICS5AcXsFZAXYUQEg==",
+ "requires": {
+ "@rollup/pluginutils": "^5.0.1",
+ "@types/resolve": "1.20.2",
+ "deepmerge": "^4.2.2",
+ "is-builtin-module": "^3.2.0",
+ "is-module": "^1.0.0",
+ "resolve": "^1.22.1"
+ }
+ },
+ "@rollup/plugin-replace": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-replace/-/plugin-replace-5.0.2.tgz",
+ "integrity": "sha512-M9YXNekv/C/iHHK+cvORzfRYfPbq0RDD8r0G+bMiTXjNGKulPnCT9O3Ss46WfhI6ZOCgApOP7xAdmCQJ+U2LAA==",
+ "requires": {
+ "@rollup/pluginutils": "^5.0.1",
+ "magic-string": "^0.27.0"
+ },
+ "dependencies": {
+ "magic-string": {
+ "version": "0.27.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.27.0.tgz",
+ "integrity": "sha512-8UnnX2PeRAPZuN12svgR9j7M1uWMovg/CEnIwIG0LFkXSJJe4PdfUGiTGl8V9bsBHFUtfVINcSyYxd7q+kx9fA==",
+ "requires": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ }
+ }
+ }
+ },
"@rollup/plugin-terser": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-terser/-/plugin-terser-0.4.0.tgz",
@@ -17781,6 +18066,11 @@
"integrity": "sha512-L28j2FcJfSZOnL1WBjDYp2vUHCeIFlyYI/53EwD/rKUBQ7MtUUfbQWiyKJGpcnv4/WgrhWsFKrcPstcAt/J0tQ==",
"optional": true
},
+ "@types/resolve": {
+ "version": "1.20.2",
+ "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz",
+ "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q=="
+ },
"@types/retry": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/@types/retry/-/retry-0.12.0.tgz",
@@ -18753,6 +19043,11 @@
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
+ "builtin-modules": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-3.3.0.tgz",
+ "integrity": "sha512-zhaCDicdLuWN5UbN5IMnFqNMhNfo919sH85y2/ea+5Yg9TsTkeZxpL+JLbp6cgYFS4sRLp3YV4S6yDuqVWHYOw=="
+ },
"builtins": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
@@ -18991,6 +19286,11 @@
"resolved": "https://registry.npmjs.org/commander/-/commander-4.0.1.tgz",
"integrity": "sha512-IPF4ouhCP+qdlcmCedhxX4xiGBPyigb8v5NeUp+0LyhwLgxMqyp3S0vl7TAPfS/hiP7FC3caI/PB9lTmP8r1NA=="
},
+ "commondir": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+ "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg=="
+ },
"compare-func": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/compare-func/-/compare-func-2.0.0.tgz",
@@ -19524,6 +19824,11 @@
"integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
"dev": true
},
+ "deepmerge": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.0.tgz",
+ "integrity": "sha512-z2wJZXrmeHdvYJp/Ux55wIjqo81G5Bp4c+oELTW+7ar6SogWHajt5a9gO3s3IDaGSAXjDk0vlQKN3rms8ab3og=="
+ },
"defaults": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz",
@@ -21999,6 +22304,14 @@
"has-tostringtag": "^1.0.0"
}
},
+ "is-builtin-module": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-3.2.1.tgz",
+ "integrity": "sha512-BSLE3HnV2syZ0FK0iMA/yUGplUeMmNz4AW5fnTunbCIqZi4vG3WjJT9FHMy5D69xmAYBHXQhJdALdpwVxV501A==",
+ "requires": {
+ "builtin-modules": "^3.3.0"
+ }
+ },
"is-callable": {
"version": "1.2.7",
"resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
@@ -22052,6 +22365,11 @@
"integrity": "sha512-ODlO0ruzhkzD3sdynIainVP5eoOFNN85rxA1+cwwnPe4dKyX0r5+hxNO5XpCrxlHcmb9vkOit9mhRD2JVuimHg==",
"optional": true
},
+ "is-module": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/is-module/-/is-module-1.0.0.tgz",
+ "integrity": "sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g=="
+ },
"is-natural-number": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-natural-number/-/is-natural-number-4.0.1.tgz",
@@ -22128,6 +22446,14 @@
"integrity": "sha512-4KPGizaVGj2LK7xwJIz8o5B2ubu1D/vcQsgOGFEDlpcvgZHto4gBnyd0ig7Ws+67ixmwKoNmu0hYnpo6AaKb5g==",
"optional": true
},
+ "is-reference": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz",
+ "integrity": "sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==",
+ "requires": {
+ "@types/estree": "*"
+ }
+ },
"is-regex": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
@@ -23011,6 +23337,14 @@
"yallist": "^4.0.0"
}
},
+ "magic-string": {
+ "version": "0.30.0",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.0.tgz",
+ "integrity": "sha512-LA+31JYDJLs82r2ScLrlz1GjSgu66ZV518eyWT+S8VhyQn/JL0u9MeBOvQMGYiPk1DBiSN9DDMOcXvigJZaViQ==",
+ "requires": {
+ "@jridgewell/sourcemap-codec": "^1.4.13"
+ }
+ },
"make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
diff --git a/package.json b/package.json
index ee211eac1..dda26a956 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,6 @@
"url": "git://github.com/adaptlearning/adapt_framework.git"
},
"scripts": {
- "preinstall": "node gitmodules.js",
"lint": "node node_modules/eslint/bin/eslint.js ./"
},
"license": "GPL-3.0",
@@ -20,10 +19,14 @@
"private": true,
"dependencies": {
"@babel/core": "^7.20.12",
+ "@babel/plugin-transform-modules-commonjs": "^7.21.2",
"@babel/plugin-transform-react-jsx": "^7.20.13",
"@babel/preset-env": "^7.20.2",
"@babel/preset-react": "^7.18.6",
"@rollup/plugin-babel": "^6.0.3",
+ "@rollup/plugin-commonjs": "^24.0.1",
+ "@rollup/plugin-node-resolve": "^15.0.1",
+ "@rollup/plugin-replace": "^5.0.2",
"@rollup/plugin-terser": "^0.4.0",
"@types/backbone": "^1.4.14",
"@types/jquery": "^3.5.11",
@@ -55,7 +58,9 @@
"less": "^3.9.0",
"load-grunt-config": "^4.0.1",
"lodash": "^4.17.19",
+ "magic-string": "^0.30.0",
"nsdeclare": "^0.1.0",
+ "resolve": "^1.22.1",
"rollup": "^2.79.1",
"time-grunt": "^2.0.0",
"underscore": "^1.13.1",
diff --git a/src/.npmrc b/src/.npmrc
new file mode 100644
index 000000000..f43c3094b
--- /dev/null
+++ b/src/.npmrc
@@ -0,0 +1 @@
+registry=https://adapt-bower-repository.herokuapp.com/npm/
diff --git a/src/core b/src/core
deleted file mode 160000
index 45b423a53..000000000
--- a/src/core
+++ /dev/null
@@ -1 +0,0 @@
-Subproject commit 45b423a5387666307452a1698d1890820642a51e
diff --git a/src/course/en/course.json b/src/course/en/course.json
deleted file mode 100755
index 512685cc0..000000000
--- a/src/course/en/course.json
+++ /dev/null
@@ -1,164 +0,0 @@
-{
- "_id": "course",
- "_type": "course",
- "_classes": "",
- "_htmlClasses": "",
- "title": "Adapt v5",
- "displayTitle": "Adapt Version 5",
- "description": "A sample course demonstrating the capabilities of the Adapt Framework",
- "body": "Welcome to the demonstration build for version 5 of the Adapt framework.",
- "instruction": "",
- "_buttons": {
- "_submit": {
- "buttonText": "Submit",
- "ariaLabel": "Submit"
- },
- "_reset": {
- "buttonText": "Reset",
- "ariaLabel": "Reset"
- },
- "_showCorrectAnswer": {
- "buttonText": "Show correct answer",
- "ariaLabel": "Show correct answer"
- },
- "_hideCorrectAnswer": {
- "buttonText": "Show your answer",
- "ariaLabel": "Show your answer"
- },
- "_showFeedback": {
- "buttonText": "Show feedback",
- "ariaLabel": "Show feedback"
- },
- "remainingAttemptsText": "attempts remaining",
- "remainingAttemptText": "final attempt",
- "disabledAriaLabel": "This button is disabled at the moment"
- },
- "_globals": {
- "_learnerInfo": {
- "id": "student.name@example.org",
- "name": "Name, Student",
- "firstname": "Student",
- "lastname": "Name"
- },
- "_accessibility": {
- "skipNavigationText": "Skip navigation",
- "_ariaLabels": {
- "answeredIncorrectly": "You answered incorrectly",
- "answeredCorrectly": "You answered correctly",
- "selectedAnswer": "selected",
- "unselectedAnswer": "not selected",
- "skipNavigation": "Skip Navigation",
- "previous": "Back",
- "navigationDrawer": "Open course resources.",
- "close": "Close",
- "closeDrawer": "Close drawer",
- "closeResources": "Close resources",
- "drawer": "Top of side drawer",
- "closePopup": "Close popup",
- "next": "Next",
- "done": "Done",
- "complete": "Completed",
- "incomplete": "Incomplete",
- "incorrect": "Incorrect",
- "correct": "Correct",
- "locked": "Locked",
- "visited": "Visited"
- }
- },
- "_extensions": {
- "_drawer": {
- "_navOrder": 100
- },
- "_navigation": {
- "_skipButton": {
- "_navOrder": -100
- },
- "_backButton": {
- "_navOrder": 0
- },
- "_spacers": [
- {
- "_navOrder": 0
- }
- ]
- }
- }
- },
- "_latestTrackingId": 19,
- "_pageLevelProgress": {
- "_isEnabled": true,
- "_showPageCompletion": false,
- "_isCompletionIndicatorEnabled": false,
- "_isShownInNavigationBar": true
- },
- "_resources": {
- "_isEnabled": true,
- "title": "Resources",
- "description": "View resources for this course by clicking here.",
- "_filterButtons": {
- "all": "All",
- "document": "Document",
- "media": "Media",
- "link": "Link"
- },
- "_filterAria": {
- "allAria": "View all resources",
- "documentAria": "View document resources",
- "mediaAria": "View media resources",
- "linkAria": "View resource links"
- },
- "_resourcesItems": [
- {
- "_type": "document",
- "title": "Vanilla Theme Swatch",
- "description": "See the swatch for the vanilla theme by clicking here.",
- "_link": "course/en/images/vanilla-swatch.jpg"
- },
- {
- "_type": "media",
- "title": "Adapt Learning YouTube Channel",
- "description": "Fancy catching up on some Adapt material? Click here.",
- "_link": "https://www.youtube.com/channel/UCW8SlSFuCc--B66Gf9fAEcQ"
- },
- {
- "_type": "link",
- "title": "Adapt Project Site",
- "description": "View the project's web site by clicking here.",
- "_link": "https://www.adaptlearning.org/"
- },
- {
- "_type": "link",
- "title": "Framework chat",
- "description": "Join the framework chat room by clicking here.",
- "_link": "https://gitter.im/adaptlearning/adapt_framework"
- }
- ]
- },
- "_start": {
- "_isEnabled": false,
- "_startIds": [
- {
- "_id": "co-05",
- "_skipIfComplete": true,
- "_className": ""
- }
- ],
- "_force": false,
- "_isMenuDisabled": false
- },
- "_assessment": {
- "_isPercentageBased": true,
- "_scoreToPass": 75,
- "_correctToPass": 75
- },
- "_bookmarking": {
- "_isEnabled": true,
- "_level": "component",
- "title": "Resume?",
- "body": "Would you like to resume the course from the location you were at last time?",
- "_buttons": {
- "yes": "Yes",
- "no": "No"
- }
- }
-}
diff --git a/src/package.json b/src/package.json
new file mode 100644
index 000000000..0c5bbd123
--- /dev/null
+++ b/src/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "adapt-client",
+ "version": "1.0.0",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "license": "GPL-3.0",
+ "dependencies": {
+ "adapt-contrib-accordion": "*",
+ "adapt-contrib-assessment": "*",
+ "adapt-contrib-assessmentResults": "*",
+ "adapt-contrib-blank": "*",
+ "adapt-contrib-bookmarking": "*",
+ "adapt-contrib-boxMenu": "*",
+ "adapt-contrib-core": "*",
+ "adapt-contrib-gmcq": "*",
+ "adapt-contrib-graphic": "*",
+ "adapt-contrib-hotgraphic": "*",
+ "adapt-contrib-languagePicker": "*",
+ "adapt-contrib-matching": "*",
+ "adapt-contrib-mcq": "*",
+ "adapt-contrib-media": "*",
+ "adapt-contrib-narrative": "*",
+ "adapt-contrib-pageLevelProgress": "*",
+ "adapt-contrib-resources": "*",
+ "adapt-contrib-slider": "*",
+ "adapt-contrib-spoor": "*",
+ "adapt-contrib-text": "*",
+ "adapt-contrib-textInput": "*",
+ "adapt-contrib-trickle": "*",
+ "adapt-contrib-tutor": "*",
+ "adapt-contrib-vanilla": "*"
+ }
+}