├── .codecov.yml ├── .dockerignore ├── .editorconfig ├── .eslintrc.js ├── .gitattributes ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── template-bug.md │ ├── template-change.md │ ├── template-epic.md │ └── template-story.md └── workflows │ ├── build.yml │ ├── e2e.yml │ └── test.yml ├── .gitignore ├── .ignore ├── .node-inspectorrc ├── .nvmrc ├── .old-tags-backup ├── AUTHORS ├── Brewfile ├── CONTRIBUTING.md ├── COPYRIGHT ├── Dockerfile ├── LICENSE ├── LICENSING ├── README-IE.md ├── README.md ├── babel.config.js ├── bin ├── build ├── checkinputs ├── debug ├── health-check ├── karma ├── lint ├── release ├── screenshot ├── serve ├── tdd ├── test ├── test-e2e ├── test-unit ├── upgrade └── validate-console-messages.sh ├── configs ├── playwright.config.ts ├── test │ ├── cypress-healthcheck.json │ ├── cypress-tutor.json │ ├── jest.config.json │ ├── jest.exercises.js │ ├── jest.setup.js │ ├── jest.shared.js │ ├── jest.style-mock.js │ ├── jest.tutor-acceptance.js │ ├── jest.tutor.js │ ├── matchers.js │ ├── mocks.js │ └── playwright.env.js ├── timezone-definitions.json └── upgrade-transforms.js ├── docker-compose.exercises.yml ├── docker-compose.override.yml ├── docker-compose.yml ├── docker-quickstart.md ├── docker ├── docker-compose.dev.yml └── nginx.conf ├── exercises ├── README.md ├── api │ └── exercises │ │ ├── 1.json │ │ └── 1@draft.json ├── index.html ├── index.js ├── resources │ └── styles │ │ ├── app.scss │ │ ├── attachments.scss │ │ ├── bootstrap.scss │ │ ├── card-background.scss │ │ ├── card-question.scss │ │ ├── exercise-preview.scss │ │ ├── exercises.scss │ │ ├── global │ │ ├── defaults.scss │ │ ├── font-awesome.scss │ │ ├── tutor-bootstrap.scss │ │ └── widgets.scss │ │ ├── mixins │ │ ├── card.scss │ │ ├── editing-panes.scss │ │ ├── generic.scss │ │ ├── quill.scss │ │ └── toolbar.scss │ │ ├── navbar.scss │ │ ├── preview.scss │ │ ├── print.scss │ │ ├── question.scss │ │ ├── tags.scss │ │ ├── variables.scss │ │ ├── variables │ │ ├── colors.scss │ │ ├── paths.scss │ │ ├── tutor-bootstrap.scss │ │ └── tutor-material.scss │ │ ├── vendor-variables.scss │ │ └── vocabulary.scss ├── specs │ ├── .eslintrc.js │ ├── components │ │ ├── __snapshots__ │ │ │ └── exercise.spec.js.snap │ │ ├── exercise.spec.js │ │ ├── exercise │ │ │ └── controls.spec.js │ │ ├── preview │ │ │ ├── __snapshots__ │ │ │ │ └── controls.spec.jsx.snap │ │ │ └── controls.spec.jsx │ │ ├── search │ │ │ ├── __snapshots__ │ │ │ │ └── clause.spec.jsx.snap │ │ │ ├── book-sections.spec.jsx │ │ │ └── clause.spec.jsx │ │ └── tags │ │ │ ├── __snapshots__ │ │ │ ├── aplo.spec.js.snap │ │ │ ├── book-selection.spec.js.snap │ │ │ ├── books.spec.js.snap │ │ │ ├── cnx-mod.spec.js.snap │ │ │ └── lo.spec.js.snap │ │ │ ├── aplo.spec.js │ │ │ ├── book-selection.spec.js │ │ │ ├── books.spec.js │ │ │ ├── cnx-mod.spec.js │ │ │ └── lo.spec.js │ ├── factories.ts │ ├── helpers.js │ └── models │ │ ├── exercises.spec.ts │ │ └── exercises │ │ └── exercise.spec.ts └── src │ ├── api.ts │ ├── api │ └── adapter.js │ ├── app.jsx │ ├── components │ ├── exercise-state.jsx │ ├── exercise.jsx │ ├── exercise │ │ ├── answer.jsx │ │ ├── attachments.jsx │ │ ├── attachments │ │ │ ├── attachment.jsx │ │ │ └── chooser.jsx │ │ ├── controls.jsx │ │ ├── mpq-toggle.jsx │ │ ├── preview.jsx │ │ ├── published-toast.js │ │ ├── question-format-type.jsx │ │ ├── question.jsx │ │ └── tags.jsx │ ├── preview.jsx │ ├── preview │ │ └── controls.jsx │ ├── search.tsx │ ├── search │ │ ├── book-sections.tsx │ │ ├── clause.jsx │ │ ├── controls.jsx │ │ └── pagination.tsx │ ├── tags │ │ ├── aacn.jsx │ │ ├── aplo.jsx │ │ ├── assignment-type.jsx │ │ ├── blooms.jsx │ │ ├── book-selection.jsx │ │ ├── books.jsx │ │ ├── cnx-feature.jsx │ │ ├── cnx-mod.jsx │ │ ├── dok.jsx │ │ ├── error.jsx │ │ ├── historical-thinking.js │ │ ├── lo.jsx │ │ ├── multi-input.jsx │ │ ├── multi-select.js │ │ ├── nclex.jsx │ │ ├── nursingBooks.js │ │ ├── public-solution-subset.jsx │ │ ├── public-solutions.jsx │ │ ├── reasoning-process.js │ │ ├── requires-context.jsx │ │ ├── science-practice.js │ │ ├── single-dropdown.tsx │ │ ├── time.jsx │ │ └── wrapper.jsx │ └── user-actions-menu.jsx │ ├── models │ ├── book.ts │ ├── books.ts │ ├── exercises.ts │ ├── exercises │ │ ├── delegation.ts │ │ ├── exercise.ts │ │ └── image.ts │ ├── search.ts │ ├── toasts.ts │ └── user.ts │ └── ux.ts ├── jenkins ├── continuous-delivery.jenkins └── continuous-integration.jenkins ├── package.json ├── script ├── bootstrap ├── build ├── ci ├── generate-tutor-boot-screen ├── setup └── test ├── shared ├── .DS_Store ├── README.md ├── api │ ├── breadcrumbs │ │ └── steps.json │ ├── exercise-multipart │ │ ├── index.js │ │ └── steps.js │ ├── exercise-preview │ │ └── data.json │ ├── exercise │ │ ├── free-response.json │ │ ├── index.js │ │ ├── multiple-choice.json │ │ └── review.json │ ├── html │ │ └── data.json │ └── notifications.json ├── demo.js ├── full-build.js ├── index.html ├── resources │ ├── images │ │ ├── icons │ │ │ ├── exercise-details-action.svg │ │ │ ├── exercise-exclude-card-view-action.svg │ │ │ ├── exercise-exclude-details-view-action.svg │ │ │ ├── exercise-feedback-off-action.svg │ │ │ ├── exercise-feedback-on-action.svg │ │ │ ├── exercise-include-action.svg │ │ │ ├── exercise-report-error-action.svg │ │ │ ├── icon-calculator.svg │ │ │ ├── icon-completed.svg │ │ │ ├── icon-correct.svg │ │ │ ├── icon-end.svg │ │ │ ├── icon-event.svg │ │ │ ├── icon-external.svg │ │ │ ├── icon-feedback.svg │ │ │ ├── icon-homework.svg │ │ │ ├── icon-incorrect.svg │ │ │ ├── icon-individual-review-heading.svg │ │ │ ├── icon-individual-review.svg │ │ │ ├── icon-interactive-placeholder.svg │ │ │ ├── icon-interactive.svg │ │ │ ├── icon-multipart.svg │ │ │ ├── icon-multiple-choice.svg │ │ │ ├── icon-personalized-intro-heading.svg │ │ │ ├── icon-personalized-intro.svg │ │ │ ├── icon-personalized-light.svg │ │ │ ├── icon-personalized.svg │ │ │ ├── icon-reading.svg │ │ │ ├── icon-recover.svg │ │ │ ├── icon-spaced-practice-intro-heading.svg │ │ │ ├── icon-spaced-practice-intro.svg │ │ │ ├── icon-spaced-practice-light.svg │ │ │ ├── icon-spaced-practice.svg │ │ │ ├── icon-test.svg │ │ │ ├── icon-two-step-intro-heading.svg │ │ │ ├── icon-two-step-intro.svg │ │ │ ├── icon-two-step.svg │ │ │ ├── icon-video-placeholder.svg │ │ │ ├── icon-video.svg │ │ │ ├── icon-worked-example.svg │ │ │ ├── icon-wrm.svg │ │ │ ├── video-placeholder-icon.svg │ │ │ └── worked-example-icon.svg │ │ └── openstax-white-books.png │ └── styles │ │ ├── _components.scss │ │ ├── components │ │ ├── breadcrumbs │ │ │ ├── icons.scss │ │ │ ├── index.scss │ │ │ └── step.scss │ │ ├── change-student-id-form.scss │ │ ├── close.scss │ │ ├── enroll │ │ │ └── new-registration.scss │ │ ├── exercise-badges │ │ │ └── index.scss │ │ ├── exercise-preview │ │ │ └── index.scss │ │ ├── exercise │ │ │ ├── group.scss │ │ │ ├── intro.scss │ │ │ └── step-card.scss │ │ ├── html │ │ │ └── index.scss │ │ ├── notifications-bar.scss │ │ ├── ox-colored-stripe.scss │ │ ├── question.scss │ │ ├── smart-overflow.scss │ │ ├── spy-mode.scss │ │ ├── surety-guard.scss │ │ └── toasts.scss │ │ ├── demo.scss │ │ ├── globals │ │ ├── externals.scss │ │ ├── icons.scss │ │ └── openstax-bootstrap.scss │ │ ├── main.scss │ │ ├── mixins │ │ ├── icon.scss │ │ ├── index.scss │ │ ├── input.scss │ │ ├── questions.scss │ │ └── triangle.scss │ │ ├── no-conflict.scss │ │ └── variables │ │ ├── colors.scss │ │ ├── index.scss │ │ ├── openstax-bootstrap.scss │ │ └── paths.scss ├── specs │ ├── .eslintrc.js │ ├── components │ │ ├── __snapshots__ │ │ │ ├── change-student-id-form.spec.js.snap │ │ │ ├── corner-ribbon.spec.jsx.snap │ │ │ ├── exercise-identifier-link.spec.js.snap │ │ │ ├── html.spec.js.snap │ │ │ ├── icon.spec.js.snap │ │ │ ├── ox-colored-stripe.spec.js.snap │ │ │ └── spy-mode.spec.jsx.snap │ │ ├── breadcrumb │ │ │ └── index.spec.js │ │ ├── buttons │ │ │ ├── __snapshots__ │ │ │ │ └── close-button.spec.js.snap │ │ │ ├── async-button.spec.js │ │ │ ├── close-button.spec.js │ │ │ └── refresh-button.spec.js │ │ ├── change-student-id-form.spec.js │ │ ├── corner-ribbon.spec.jsx │ │ ├── enroll │ │ │ ├── confirm-join-course.spec.js │ │ │ └── message-list.spec.js │ │ ├── exercise-badges │ │ │ ├── __snapshots__ │ │ │ │ └── index.spec.js.snap │ │ │ └── index.spec.js │ │ ├── exercise-identifier-link.spec.js │ │ ├── exercise-preview │ │ │ ├── __snapshots__ │ │ │ │ └── index.spec.js.snap │ │ │ └── index.spec.js │ │ ├── exercise │ │ │ └── step-data.json │ │ ├── html.spec.js │ │ ├── icon.spec.js │ │ ├── notifications │ │ │ ├── __snapshots__ │ │ │ │ ├── bar.spec.js.snap │ │ │ │ └── course-has-ended.spec.js.snap │ │ │ ├── bar.spec.js │ │ │ ├── course-has-ended.spec.js │ │ │ ├── email.spec.js │ │ │ └── system.spec.js │ │ ├── ox-colored-stripe.spec.js │ │ ├── popout-window.spec.jsx │ │ ├── question │ │ │ ├── __snapshots__ │ │ │ │ ├── answer.spec.js.snap │ │ │ │ └── index.spec.js.snap │ │ │ ├── answer.spec.js │ │ │ └── index.spec.js │ │ ├── spy-mode.spec.jsx │ │ ├── static.spec.jsx │ │ └── surety-guard.spec.js │ ├── factories │ │ ├── exercise.js │ │ ├── helpers.js │ │ └── index.ts │ ├── helpers │ │ ├── api-mock.ts │ │ ├── errors.ts │ │ ├── exercise.spec.js │ │ ├── fake-dom-node.js │ │ ├── fake-window.js │ │ ├── html-videos.js │ │ ├── index.ts │ │ ├── logging.spec.js │ │ ├── mathjax.spec.ts │ │ └── utilities.js │ └── model │ │ ├── api.spec.ts │ │ ├── exercise.spec.ts │ │ ├── exercise │ │ ├── answer.spec.ts │ │ ├── question.spec.ts │ │ ├── tag.spec.ts │ │ └── tags-association.spec.ts │ │ ├── map.spec.ts │ │ ├── model.spec.ts │ │ ├── networking.spec.ts │ │ ├── notifications.spec.ts │ │ ├── notifications │ │ └── pollers.spec.ts │ │ ├── time.spec.ts │ │ ├── ui-settings.spec.ts │ │ ├── urls.spec.ts │ │ └── user.spec.ts └── src │ ├── api │ └── request.ts │ ├── components │ ├── breadcrumb │ │ └── index.js │ ├── buttons │ │ ├── async-button.js │ │ ├── close-button.js │ │ └── refresh-button.js │ ├── change-student-id-form.js │ ├── corner-ribbon.jsx │ ├── enroll │ │ ├── cc-conflict-message.js │ │ ├── cc-join-conflict.js │ │ ├── confirm-join-course.js │ │ └── message-list.js │ ├── exercise-badges │ │ ├── index.jsx │ │ ├── interactive-icon.jsx │ │ └── multipart-icon.jsx │ ├── exercise-preview │ │ ├── controls-overlay.js │ │ └── index.jsx │ ├── exercise-suggest-correction-link.js │ ├── get-position-mixin.js │ ├── html.js │ ├── icon.js │ ├── icons │ │ └── glasses.js │ ├── loading-animation.js │ ├── logos │ │ ├── concept-coach-horizontal.js │ │ └── tutor-horizontal.js │ ├── markdown.js │ ├── notifications │ │ ├── bar.js │ │ ├── course-has-ended.js │ │ ├── email.js │ │ └── system.js │ ├── ox-colored-stripe.js │ ├── pagination.js │ ├── popout-window.jsx │ ├── question │ │ ├── answer.jsx │ │ ├── answers-table.jsx │ │ ├── feedback.jsx │ │ ├── formats-listing.jsx │ │ ├── index.jsx │ │ └── instructions.jsx │ ├── resize-listener-mixin.js │ ├── scroll-to-mixin.js │ ├── scroll-to-top.js │ ├── spy-mode.jsx │ ├── static.js │ ├── staxly-animation.js │ ├── surety-guard.js │ ├── toasts.jsx │ └── toasts │ │ └── index.jsx │ ├── factories │ ├── button-link.js │ └── link.js │ ├── helpers │ ├── book-uuid-xrefs.js │ ├── collections.ts │ ├── exercise.ts │ ├── html-videos.js │ ├── keys.js │ ├── logging.js │ ├── match-by-router.js │ ├── mathjax.ts │ ├── props.js │ ├── react.js │ ├── render-root.js │ ├── router.js │ └── string.ts │ ├── index.js │ ├── mixins │ └── ScrollListener.js │ ├── model.ts │ ├── model │ ├── api.ts │ ├── bignum.ts │ ├── exercise.ts │ ├── exercise │ │ ├── answer.ts │ │ ├── attachment.ts │ │ ├── author.ts │ │ ├── format.ts │ │ ├── question.ts │ │ ├── solution.ts │ │ ├── tag-association.js │ │ ├── tag.js │ │ ├── tag.ts │ │ └── tags-association.ts │ ├── map.ts │ ├── networking.ts │ ├── notifications.ts │ ├── notifications │ │ └── pollers.ts │ ├── time.ts │ ├── toasts.ts │ ├── ui-settings.ts │ ├── urls.ts │ └── user.ts │ └── types.ts ├── tsconfig.json ├── tutor ├── api │ ├── courses │ │ ├── 1 │ │ │ ├── cc │ │ │ │ └── dashboard.json │ │ │ ├── dashboard.json │ │ │ ├── guide.json │ │ │ ├── performance.json │ │ │ ├── plans.json │ │ │ ├── plans │ │ │ │ └── POST.json │ │ │ ├── practice.json │ │ │ ├── practice │ │ │ │ └── POST.json │ │ │ ├── roster.json │ │ │ ├── students.json │ │ │ └── tasks.json │ │ ├── 2 │ │ │ ├── dashboard.json │ │ │ ├── guide.json │ │ │ ├── performance.json │ │ │ ├── plans.json │ │ │ ├── practice.json │ │ │ ├── practice │ │ │ │ └── POST.json │ │ │ └── tasks.json │ │ ├── 3 │ │ │ └── dashboard.json │ │ ├── 4 │ │ │ └── dashboard.json │ │ ├── 22 │ │ │ └── performance.json │ │ ├── 999 │ │ │ └── dashboard.json │ │ └── 1.json │ ├── ecosystems.json │ ├── ecosystems │ │ ├── 1 │ │ │ ├── exercises.json │ │ │ └── readings.json │ │ ├── 2 │ │ │ ├── exercises.json │ │ │ └── readings.json │ │ └── 3 │ │ │ ├── exercises.json │ │ │ └── readings.json │ ├── enrollment_changes │ │ ├── POST.json │ │ └── POST_WITH_CONFLICT.json │ ├── exercises.json │ ├── fake-multipart-reading.json │ ├── notes.json │ ├── notifications.json │ ├── offerings.json │ ├── pages │ │ ├── 0e58aa87-2e09-40a7-8bf3-269b2fa16509.json │ │ ├── 17f6ff53-2d92-4669-acdd-9a958ea7fd0a@12.json │ │ ├── 334f8b61-30eb-4475-8e05-5260a4866b4b@4.68.json │ │ ├── be8818d0-2dba-4bf3-859a-737c25fb2c99@20.json │ │ ├── c20c3501-a63c-4b40-aa6c-0bdafa8a2d33.json │ │ ├── d419f72d-3349-4449-ab34-c705409df4aa@5.json │ │ ├── dummy-physics-page.json │ │ └── newpagebio.json │ ├── plans │ │ ├── 1 │ │ │ ├── review.json │ │ │ └── stats.json │ │ ├── 2 │ │ │ └── stats.json │ │ ├── 3 │ │ │ └── stats.json │ │ ├── 4 │ │ │ ├── review.json │ │ │ └── stats.json │ │ ├── 5 │ │ │ ├── review.json │ │ │ └── stats.json │ │ ├── 6 │ │ │ └── stats.json │ │ ├── 7 │ │ │ └── stats.json │ │ ├── 8 │ │ │ └── stats.json │ │ ├── 9 │ │ │ └── stats.json │ │ ├── 13 │ │ │ └── stats.json │ │ ├── 1.json │ │ ├── 13.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 5.json │ │ ├── 7.json │ │ └── 8.json │ ├── purchases.json │ ├── steps │ │ ├── step-id-1-1.json │ │ ├── step-id-2-1.json │ │ ├── step-id-3-1.json │ │ ├── step-id-4-1.json │ │ ├── step-id-4-2.json │ │ ├── step-id-4-2 │ │ │ ├── PATCH.json │ │ │ ├── completed │ │ │ │ └── PUT.json │ │ │ └── recovery │ │ │ │ └── PUT.json │ │ ├── step-id-4-3.json │ │ ├── step-id-4-3 │ │ │ └── completed │ │ │ │ └── PUT.json │ │ ├── step-id-4-4.json │ │ ├── step-id-4-4 │ │ │ └── completed │ │ │ │ └── PUT.json │ │ ├── step-id-4-5.json │ │ ├── step-id-4-5 │ │ │ ├── PATCH.json │ │ │ └── completed │ │ │ │ └── PUT.json │ │ ├── step-id-5-1.json │ │ ├── step-id-5-2.json │ │ ├── step-id-5-3.json │ │ ├── step-id-6-1.json │ │ ├── step-id-6-2.json │ │ ├── step-id-6-3.json │ │ ├── step-id-6-4-full.json │ │ ├── step-id-6-4.json │ │ ├── step-id-7-1.json │ │ ├── step-id-7-2.json │ │ ├── step-id-7-3.json │ │ ├── step-id-7-4.json │ │ ├── step-id-8-1.json │ │ ├── step-id-9-1.json │ │ ├── step-id-9-1 │ │ │ ├── PATCH.json │ │ │ └── completed │ │ │ │ └── PUT.json │ │ ├── step-id-9-2.json │ │ ├── step-id-9-2 │ │ │ ├── PATCH.json │ │ │ └── completed │ │ │ │ └── PUT.json │ │ ├── step-id-9-3.json │ │ ├── step-id-9-3 │ │ │ └── recovery │ │ │ │ └── PUT.json │ │ ├── step-id-9-4.json │ │ ├── step-id-9-5.json │ │ ├── step-id-practice-4.json │ │ └── step-id-practice-5.json │ ├── tasks │ │ ├── 1.json │ │ ├── 2.json │ │ ├── 3.json │ │ ├── 4-recovered.json │ │ ├── 4.json │ │ ├── 5.json │ │ ├── 6.json │ │ ├── 7.json │ │ ├── 8.json │ │ ├── 9.json │ │ └── Practice-Course-1.json │ ├── user.json │ └── user │ │ ├── courses.json │ │ └── courses │ │ ├── 1 │ │ └── guide.json │ │ ├── 1.json │ │ └── no-periods.json ├── index.html ├── index.js ├── jest-playwright.config.js ├── lms-pair.js ├── resources │ ├── fonts │ │ ├── architects-daughter-v6-latin-regular.woff │ │ ├── architects-daughter-v6-latin-regular.woff2 │ │ ├── merriweather-v8-latin-300.woff │ │ ├── merriweather-v8-latin-300.woff2 │ │ ├── merriweather-v8-latin-300italic.woff │ │ ├── merriweather-v8-latin-300italic.woff2 │ │ ├── merriweather-v8-latin-700.woff │ │ ├── merriweather-v8-latin-700.woff2 │ │ ├── merriweather-v8-latin-700italic.woff │ │ ├── merriweather-v8-latin-700italic.woff2 │ │ ├── merriweather-v8-latin-regular.woff │ │ ├── merriweather-v8-latin-regular.woff2 │ │ ├── merriweather-v8-latin-regularitalic.woff │ │ └── merriweather-v8-latin-regularitalic.woff2 │ ├── images │ │ ├── cc-sunset │ │ │ ├── export-by.svg │ │ │ ├── thanks.svg │ │ │ ├── under-construction.svg │ │ │ └── view-analytics.svg │ │ ├── course-preview │ │ │ ├── all-features.svg │ │ │ ├── cant-save-work.svg │ │ │ ├── view-analytics.svg │ │ │ └── view-textbook-questions.svg │ │ ├── courses │ │ │ ├── american-government-3e-book-thumbnail.svg │ │ │ ├── anatomy_physiology-thumbnail.svg │ │ │ ├── ap_physics-book-thumbnail.svg │ │ │ ├── ap_us_history-book-thumbnail.svg │ │ │ ├── biology-background.jpg │ │ │ ├── biology-book-thumbnail.svg │ │ │ ├── biology-title-background.svg │ │ │ ├── biology_2e-book-thumbnail.svg │ │ │ ├── college_physics-book-thumbnail.svg │ │ │ ├── concepts_biology-book-thumbnail.png │ │ │ ├── entrepreneurship-book-thumbnail.svg │ │ │ ├── hs_physics-book-thumbnail.png │ │ │ ├── hs_physics-title-background.svg │ │ │ ├── physics-background.jpg │ │ │ ├── psychology_2e.svg │ │ │ ├── sociology-3e-book-thumbnail.svg │ │ │ ├── sociology-book-thumbnail.svg │ │ │ └── us-history-book-thumbnail.svg │ │ ├── highlighter │ │ │ ├── instructions-highlighter-icon.svg │ │ │ ├── instructions-notes-icon.svg │ │ │ └── instructions-panel-icon.svg │ │ ├── icons │ │ │ ├── best-practices.svg │ │ │ ├── exercise-copy-edit-action.svg │ │ │ ├── exercise-details-action.svg │ │ │ ├── exercise-exclude-card-view-action.svg │ │ │ ├── exercise-exclude-details-view-action.svg │ │ │ ├── exercise-feedback-off-action.svg │ │ │ ├── exercise-feedback-on-action.svg │ │ │ ├── exercise-include-action.svg │ │ │ ├── exercise-report-error-action.svg │ │ │ ├── icon-copy-course.svg │ │ │ ├── icon-new-course.svg │ │ │ ├── mpp-exclude-action.svg │ │ │ ├── mpp-selected-question.svg │ │ │ └── pending-verification.svg │ │ ├── learning-guide │ │ │ ├── cloud.svg │ │ │ ├── flag-blue.svg │ │ │ ├── flag-green.svg │ │ │ ├── flag-grey.svg │ │ │ ├── flag-yellow.svg │ │ │ ├── guide-pencil.svg │ │ │ ├── houston-skyline.svg │ │ │ └── openstax-plane.svg │ │ ├── openstax-logo-vertical.svg │ │ ├── openstax-logo-white.png │ │ ├── openstax-logo.svg │ │ ├── openstax-tutor-beta-logo.svg │ │ ├── ost-hero.jpg │ │ ├── question-library │ │ │ ├── exclude-question.svg │ │ │ ├── machine-learning.svg │ │ │ └── question-details.svg │ │ ├── rice-logo.png │ │ ├── student-id.svg │ │ └── value-prop │ │ │ ├── low-cost.svg │ │ │ ├── personalized.svg │ │ │ ├── spaced-practice.svg │ │ │ ├── tutor-background.svg │ │ │ └── two-step.svg │ └── styles │ │ ├── book-content │ │ ├── base.scss │ │ ├── equations.scss │ │ ├── examples.scss │ │ ├── figure.scss │ │ ├── index.scss │ │ ├── learning-objectives.scss │ │ ├── link-to-learning.scss │ │ ├── mixins.scss │ │ ├── note.scss │ │ ├── os-teacher.scss │ │ ├── page-title.scss │ │ ├── page.scss │ │ ├── questions.scss │ │ ├── splash-image.scss │ │ ├── target.scss │ │ ├── theming-mixins.scss │ │ ├── typography.scss │ │ └── variables.scss │ │ ├── components │ │ ├── app.scss │ │ ├── assignment-links.scss │ │ ├── book-menu.scss │ │ ├── branding │ │ │ └── course.scss │ │ ├── cc-student-redirect.scss │ │ ├── choices-listing.scss │ │ ├── confirm-leave-mixin.scss │ │ ├── copy-on-focus-input.scss │ │ ├── course-page.scss │ │ ├── course-roster.scss │ │ ├── course-title-banner.scss │ │ ├── date-time-input.scss │ │ ├── dialog.scss │ │ ├── enrollment-student-id.scss │ │ ├── exercise-preview.scss │ │ ├── exercises.scss │ │ ├── html.scss │ │ ├── icon.scss │ │ ├── index.scss │ │ ├── info-page.scss │ │ ├── invalid-page.scss │ │ ├── markdown.scss │ │ ├── media-preview.scss │ │ ├── modals.scss │ │ ├── modals │ │ │ ├── error.scss │ │ │ ├── onboarding.scss │ │ │ ├── terms.scss │ │ │ └── warning.scss │ │ ├── multi-select.scss │ │ ├── my-courses.scss │ │ ├── no-periods.scss │ │ ├── notes │ │ │ ├── note.scss │ │ │ └── summary-page.scss │ │ ├── paging-navigation.scss │ │ ├── payments.scss │ │ ├── plan-stats.scss │ │ ├── popover.scss │ │ ├── practice-button.scss │ │ ├── registration │ │ │ └── index.scss │ │ ├── student-preview.scss │ │ ├── tabs.scss │ │ ├── times.scss │ │ ├── toasts.scss │ │ ├── top-nav-bar.scss │ │ ├── top-nav-bar │ │ │ ├── center-controls.scss │ │ │ ├── drop-down-menu.scss │ │ │ ├── plugable.scss │ │ │ ├── preview-add-course-btn.scss │ │ │ └── question-library.scss │ │ ├── tours.scss │ │ ├── tours │ │ │ ├── anchor.scss │ │ │ ├── centered-wheel.scss │ │ │ ├── joyride.scss │ │ │ ├── mini-notice.scss │ │ │ ├── navbar.scss │ │ │ ├── new-enrollment-link.scss │ │ │ ├── super-training-wheel.scss │ │ │ ├── tips-now-or-later.scss │ │ │ └── value-prop.scss │ │ ├── tri-state-checkbox.scss │ │ ├── tutor-dialog.scss │ │ └── tutor-input.scss │ │ ├── definitions.scss │ │ ├── global.scss │ │ ├── global │ │ ├── alerts.scss │ │ ├── buttons.scss │ │ ├── card.scss │ │ ├── externals.scss │ │ ├── forms.scss │ │ ├── links.scss │ │ ├── maths.scss │ │ ├── menus.scss │ │ ├── misc.scss │ │ ├── navbar.scss │ │ ├── notifications-bar.scss │ │ ├── popover.scss │ │ ├── progress-bars.scss │ │ ├── scaffold.scss │ │ ├── tooltip.scss │ │ ├── tutor-booksplash-background.scss │ │ ├── tutor-bootstrap.scss │ │ └── typography.scss │ │ ├── mixins.scss │ │ ├── mixins │ │ ├── drag-and-drop.scss │ │ ├── exercise-cards-column-layout.scss │ │ ├── figures.scss │ │ ├── left-stripe.scss │ │ ├── performance-forecast.scss │ │ ├── tasks.scss │ │ ├── tutor-modal.scss │ │ └── tutor-plans.scss │ │ ├── tutor.scss │ │ ├── variables.scss │ │ └── variables │ │ ├── book-content.scss │ │ ├── colors.scss │ │ ├── paths.scss │ │ ├── registration.scss │ │ └── tutor-bootstrap.scss ├── specs │ ├── .eslintrc.js │ ├── acceptance │ │ ├── .eslintrc.js │ │ ├── dashboard.spec.js │ │ ├── helpers │ │ │ ├── global-teardown.js │ │ │ ├── images-dir.js │ │ │ ├── index.js │ │ │ ├── jest-environment.js │ │ │ ├── open-page.js │ │ │ ├── set-role.js │ │ │ ├── snapshot-page.js │ │ │ └── working-directory.js │ │ ├── my-courses.spec.js │ │ ├── setup.js │ │ └── survey.spec.js │ ├── components │ │ ├── __snapshots__ │ │ │ ├── app.spec.jsx.snap │ │ │ ├── best-practices-tip.spec.js.snap │ │ │ ├── change-student-id.spec.jsx.snap │ │ │ ├── checkbox-input.spec.js.snap │ │ │ ├── content-page.spec.js.snap │ │ │ ├── course-breadcrumb.spec.jsx.snap │ │ │ ├── course-page.spec.jsx.snap │ │ │ ├── date-time-input.spec.js.snap │ │ │ ├── enroll.spec.jsx.snap │ │ │ ├── go-to-top.spec.js.snap │ │ │ ├── invalid-page.spec.jsx.snap │ │ │ ├── lms-info-card.spec.jsx.snap │ │ │ ├── multi-select.spec.jsx.snap │ │ │ ├── my-courses.spec.jsx.snap │ │ │ ├── navbar.spec.js.snap │ │ │ ├── notes.spec.js.snap │ │ │ ├── number-input.spec.js.snap │ │ │ ├── paging-navigation.spec.jsx.snap │ │ │ ├── radio-input.spec.jsx.snap │ │ │ ├── related-content-link.spec.js.snap │ │ │ ├── sections-filter.spec.jsx.snap │ │ │ ├── select.spec.jsx.snap │ │ │ ├── tag-list.spec.jsx.snap │ │ │ ├── task-progress.spec.js.snap │ │ │ ├── template-modal.spec.js.snap │ │ │ ├── time-input.spec.js.snap │ │ │ └── tutor-layout.spec.js.snap │ │ ├── app.spec.jsx │ │ ├── best-practices-tip.spec.js │ │ ├── book-menu │ │ │ └── menu.spec.js │ │ ├── buttons │ │ │ ├── __snapshots__ │ │ │ │ ├── browse-the-book.spec.jsx.snap │ │ │ │ ├── reload-page.spec.js.snap │ │ │ │ └── save-practice.spec.tsx.snap │ │ │ ├── browse-the-book.spec.jsx │ │ │ ├── reload-page.spec.js │ │ │ ├── save-practice.spec.tsx │ │ │ └── teacher-becomes-student.spec.js │ │ ├── change-student-id.spec.jsx │ │ ├── checkbox-input.spec.js │ │ ├── content-page.spec.js │ │ ├── copy-on-focus-input.spec.jsx │ │ ├── course-breadcrumb.spec.jsx │ │ ├── course-nag.spec.jsx │ │ ├── course-page.spec.jsx │ │ ├── date-time-input.spec.js │ │ ├── enroll.spec.jsx │ │ ├── error-monitoring │ │ │ ├── __snapshots__ │ │ │ │ └── async-load-error.spec.jsx.snap │ │ │ ├── async-load-error.spec.jsx │ │ │ ├── boundary.spec.js │ │ │ ├── handlers.spec.jsx │ │ │ ├── index.spec.js │ │ │ └── server-error-message.spec.jsx │ │ ├── exercises │ │ │ ├── __snapshots__ │ │ │ │ ├── preview.spec.js.snap │ │ │ │ └── sectionizer.spec.js.snap │ │ │ ├── cards.spec.js │ │ │ ├── details.spec.jsx │ │ │ ├── preview.spec.js │ │ │ └── sectionizer.spec.js │ │ ├── go-to-top.spec.js │ │ ├── helpers │ │ │ └── utilities.js │ │ ├── icons │ │ │ └── lock.spec.js │ │ ├── invalid-page.spec.jsx │ │ ├── lms-info-card.spec.jsx │ │ ├── modal-manager.spec.jsx │ │ ├── multi-select.spec.jsx │ │ ├── my-courses.spec.jsx │ │ ├── my-courses │ │ │ ├── __snapshots__ │ │ │ │ └── pending-verification.spec.jsx.snap │ │ │ └── pending-verification.spec.jsx │ │ ├── name.spec.js │ │ ├── navbar.spec.js │ │ ├── navbar │ │ │ ├── __snapshots__ │ │ │ │ ├── actions-menu.spec.jsx.snap │ │ │ │ ├── background-toasts.spec.jsx.snap │ │ │ │ └── preview-add-course-btn.spec.jsx.snap │ │ │ ├── account-link.spec.js │ │ │ ├── actions-menu.spec.jsx │ │ │ ├── background-toasts.spec.jsx │ │ │ ├── center-controls.spec.jsx │ │ │ ├── preview-add-course-btn.spec.jsx │ │ │ ├── student-pay-now-btn.spec.jsx │ │ │ └── support-menu.spec.jsx │ │ ├── notes.spec.js │ │ ├── notes │ │ │ ├── __snapshots__ │ │ │ │ ├── note-card.spec.js.snap │ │ │ │ └── summary-popup.spec.js.snap │ │ │ ├── note-card.spec.js │ │ │ ├── summary-page.spec.js │ │ │ └── summary-popup.spec.js │ │ ├── number-input.spec.js │ │ ├── obscured-page.spec.js │ │ ├── onboarding │ │ │ ├── __snapshots__ │ │ │ │ ├── course-use-tips.spec.jsx.snap │ │ │ │ ├── expired-preview-warning.spec.jsx.snap │ │ │ │ ├── free-trial-activated.spec.jsx.snap │ │ │ │ ├── free-trial-ended.spec.jsx.snap │ │ │ │ ├── freshly-created-course.spec.jsx.snap │ │ │ │ ├── make-payment.spec.jsx.snap │ │ │ │ ├── pay-now-or-later.spec.jsx.snap │ │ │ │ ├── preview-only-warning.spec.jsx.snap │ │ │ │ ├── redeem-code.spec.jsx.snap │ │ │ │ └── second-session-warning.spec.jsx.snap │ │ │ ├── course-use-tips.spec.jsx │ │ │ ├── expired-preview-warning.spec.jsx │ │ │ ├── free-trial-activated.spec.jsx │ │ │ ├── free-trial-ended.spec.jsx │ │ │ ├── freshly-created-course.spec.jsx │ │ │ ├── make-payment.spec.jsx │ │ │ ├── pay-now-or-later.spec.jsx │ │ │ ├── preview-only-warning.spec.jsx │ │ │ ├── redeem-code.spec.jsx │ │ │ ├── second-session-warning.spec.jsx │ │ │ └── ux │ │ │ │ ├── base.spec.ts │ │ │ │ ├── index.spec.ts │ │ │ │ ├── preview.spec.ts │ │ │ │ └── student-course.spec.ts │ │ ├── paging-navigation.spec.jsx │ │ ├── payments │ │ │ ├── __snapshots__ │ │ │ │ └── manage.spec.jsx.snap │ │ │ └── manage.spec.jsx │ │ ├── plan-stats │ │ │ ├── __snapshots__ │ │ │ │ └── progress.spec.jsx.snap │ │ │ ├── index.spec.jsx │ │ │ └── progress.spec.jsx │ │ ├── radio-input.spec.jsx │ │ ├── related-content-link.spec.js │ │ ├── responsive.spec.js │ │ ├── sections-chooser.spec.jsx │ │ ├── sections-filter.spec.jsx │ │ ├── select.spec.jsx │ │ ├── tabs.spec.js │ │ ├── tag-list.spec.jsx │ │ ├── task-progress.spec.js │ │ ├── template-modal.spec.js │ │ ├── terms-modal.spec.jsx │ │ ├── time-input.spec.js │ │ ├── time.spec.js │ │ ├── toasts │ │ │ ├── __snapshots__ │ │ │ │ └── lms.spec.js.snap │ │ │ ├── lms.spec.js │ │ │ └── scores.spec.js │ │ ├── tours │ │ │ ├── anchor.spec.jsx │ │ │ ├── conductor.spec.jsx │ │ │ ├── custom │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── instructor-welcome-to-tutor.spec.jsx.snap │ │ │ │ └── instructor-welcome-to-tutor.spec.jsx │ │ │ └── region.spec.jsx │ │ ├── tutor-dialog.spec.js │ │ ├── tutor-layout.spec.js │ │ └── warning-model.spec.jsx │ ├── courses-test-data.js │ ├── e2e │ │ ├── add-edit-question.e2e.ts │ │ ├── assignment-review.e2e.ts │ │ ├── browse-the-book.e2e.ts │ │ ├── course-roster.e2e.ts │ │ ├── course-settings.e2e.ts │ │ ├── create-course.e2e.ts │ │ ├── helpers.ts │ │ ├── mocker.ts │ │ ├── multiple-attempts.e2e.ts │ │ ├── my-courses.e2e.ts │ │ ├── preview-courses.e2e.ts │ │ ├── question-library.e2e.ts │ │ ├── setup.ts │ │ ├── shuffle-answer-choices.e2e.ts │ │ ├── soc3e.e2e.ts │ │ ├── student-dashboard.e2e.ts │ │ ├── student-task.e2e.ts │ │ ├── teacher-gradebook.e2e.ts │ │ ├── test.ts │ │ └── webpack.config.js │ ├── factories │ │ ├── .eslintrc.js │ │ ├── book.js │ │ ├── bootstrap.js │ │ ├── course-dashboard.js │ │ ├── course-roster.js │ │ ├── course.js │ │ ├── definitions.js │ │ ├── ecosystem.js │ │ ├── exercise.js │ │ ├── grading-template.js │ │ ├── helpers.js │ │ ├── index.ts │ │ ├── note.js │ │ ├── offering.js │ │ ├── practice-questions.js │ │ ├── research_survey.js │ │ ├── scores.js │ │ ├── stats.js │ │ ├── student-task-models.ts │ │ ├── student-tasks.js │ │ ├── task-plan-stats.js │ │ ├── task-scores.js │ │ ├── teacher-task-plan.js │ │ └── user.js │ ├── global.d.ts │ ├── helpers │ │ ├── analytics.spec.js │ │ ├── content.spec.js │ │ ├── context.js │ │ ├── course-data.spec.js │ │ ├── course-enrollment.tsx │ │ ├── dates.spec.js │ │ ├── dom.spec.js │ │ ├── images-complete.spec.js │ │ ├── index.ts │ │ ├── modal-wrapper.js │ │ ├── object.spec.js │ │ ├── offering.spec.js │ │ ├── pardot.spec.ts │ │ ├── payments.spec.ts │ │ ├── period.spec.js │ │ ├── reference-book-base-ux.spec.ts │ │ ├── reload.spec.js │ │ ├── scores-data.js │ │ ├── scores.spec.js │ │ ├── scroll-to.spec.js │ │ ├── string.spec.js │ │ ├── test-router.js │ │ ├── time-mock.ts │ │ └── time.spec.js │ ├── integration │ │ ├── assignment-edit.spec.js │ │ ├── assignment-grade.spec.js │ │ ├── assignment-review.spec.js │ │ ├── course.spec.js │ │ ├── dashboard.spec.js │ │ ├── grading-templates.spec.js │ │ ├── helpers.js │ │ ├── my-practice-question.spec.js │ │ ├── student-dashboard.spec.js │ │ ├── student-gradebook.spec.js │ │ ├── student-task.spec.js │ │ └── teacher-gradebook.spec.js │ ├── models │ │ ├── app.spec.ts │ │ ├── chapter-section.spec.ts │ │ ├── course.spec.ts │ │ ├── courses │ │ │ ├── create.spec.ts │ │ │ ├── information.spec.ts │ │ │ ├── offerings.spec.ts │ │ │ ├── period.spec.ts │ │ │ ├── roster.spec.ts │ │ │ ├── student.spec.ts │ │ │ └── ux.spec.ts │ │ ├── exercises.spec.ts │ │ ├── feature_flags.spec.ts │ │ ├── grading │ │ │ ├── __snapshots__ │ │ │ │ └── templates.spec.ts.snap │ │ │ └── templates.spec.ts │ │ ├── job.spec.ts │ │ ├── jobs │ │ │ ├── lms-score-push.spec.ts │ │ │ ├── scores-export.spec.ts │ │ │ └── task-plan-publish.spec.ts │ │ ├── loader.spec.ts │ │ ├── notes.spec.ts │ │ ├── purchases.spec.ts │ │ ├── purchases │ │ │ └── purchase.spec.ts │ │ ├── reference-book.spec.ts │ │ ├── reference-book │ │ │ └── node.spec.ts │ │ ├── response_validation.spec.ts │ │ ├── scores.spec.ts │ │ ├── student-tasks.spec.ts │ │ ├── student-tasks │ │ │ ├── step.spec.ts │ │ │ └── task.spec.ts │ │ ├── task-plans │ │ │ ├── student.spec.ts │ │ │ ├── student │ │ │ │ └── task.spec.ts │ │ │ ├── teacher.spec.ts │ │ │ └── teacher │ │ │ │ ├── grade.spec.ts │ │ │ │ ├── past.spec.ts │ │ │ │ ├── plan.spec.ts │ │ │ │ ├── stats.spec.ts │ │ │ │ └── tasking.spec.ts │ │ ├── tour.spec.ts │ │ ├── tour │ │ │ ├── context.spec.ts │ │ │ ├── region.spec.ts │ │ │ ├── ride.spec.ts │ │ │ └── step.spec.ts │ │ ├── user.spec.ts │ │ ├── user │ │ │ ├── __snapshots__ │ │ │ │ └── menu.spec.ts.snap │ │ │ ├── menu.spec.ts │ │ │ └── terms.spec.ts │ │ └── window-size.spec.ts │ ├── screens │ │ ├── assignment-edit │ │ │ ├── __snapshots__ │ │ │ │ └── questions-overview.spec.jsx.snap │ │ │ ├── homework │ │ │ │ ├── __snapshots__ │ │ │ │ │ └── exercise-controls.spec.jsx.snap │ │ │ │ └── exercise-controls.spec.jsx │ │ │ ├── questions-overview.spec.jsx │ │ │ └── ux.spec.jsx │ │ ├── course-roster │ │ │ ├── add-teacher-link.spec.jsx │ │ │ ├── bootstrap-data.js │ │ │ ├── roster.spec.jsx │ │ │ └── undrop-student.spec.jsx │ │ ├── course-settings │ │ │ ├── __snapshots__ │ │ │ │ ├── details.spec.tsx.snap │ │ │ │ └── student-access.spec.jsx.snap │ │ │ ├── details.spec.tsx │ │ │ └── student-access.spec.jsx │ │ ├── grading-templates │ │ │ ├── __snapshots__ │ │ │ │ ├── card.spec.js.snap │ │ │ │ └── modals.spec.jsx.snap │ │ │ ├── card.spec.js │ │ │ └── modals.spec.jsx │ │ ├── lms-pair │ │ │ ├── __snapshots__ │ │ │ │ └── index.spec.js.snap │ │ │ └── index.spec.js │ │ ├── new-course │ │ │ ├── __snapshots__ │ │ │ │ ├── build-course.spec.jsx.snap │ │ │ │ ├── course-name.spec.js.snap │ │ │ │ ├── course-numbers.spec.jsx.snap │ │ │ │ ├── index.spec.jsx.snap │ │ │ │ ├── offering-title.spec.jsx.snap │ │ │ │ ├── select-course.spec.jsx.snap │ │ │ │ ├── select-dates.spec.jsx.snap │ │ │ │ ├── sociology-3e-nudge.spec.js.snap │ │ │ │ └── wizard.spec.jsx.snap │ │ │ ├── build-course.spec.jsx │ │ │ ├── course-name.spec.js │ │ │ ├── course-numbers.spec.jsx │ │ │ ├── index.spec.jsx │ │ │ ├── offering-title.spec.jsx │ │ │ ├── select-course.spec.jsx │ │ │ ├── select-dates.spec.jsx │ │ │ ├── sociology-3e-nudge.spec.js │ │ │ ├── ux.spec.js │ │ │ └── wizard.spec.jsx │ │ ├── performance-forecast │ │ │ ├── __snapshots__ │ │ │ │ └── index.spec.js.snap │ │ │ ├── chapter.spec.js │ │ │ ├── index.spec.js │ │ │ ├── practice-button.spec.js │ │ │ ├── section.spec.js │ │ │ ├── weaker-panel.spec.js │ │ │ └── weaker-sections.spec.js │ │ ├── qa-view.spec.jsx │ │ ├── question-library │ │ │ ├── dashboard.spec.jsx │ │ │ └── index.spec.jsx │ │ ├── reference-book │ │ │ ├── __snapshots__ │ │ │ │ ├── book-title.spec.js.snap │ │ │ │ └── section-title.spec.js.snap │ │ │ ├── book-title.spec.js │ │ │ ├── index.spec.js │ │ │ └── section-title.spec.js │ │ ├── student-dashboard │ │ │ ├── __snapshots__ │ │ │ │ ├── dashboard.spec.jsx.snap │ │ │ │ ├── event-row.spec.jsx.snap │ │ │ │ ├── homework-row.spec.jsx.snap │ │ │ │ └── reading-row.spec.jsx.snap │ │ │ ├── dashboard.spec.jsx │ │ │ ├── empty-panel.spec.js │ │ │ ├── event-row.spec.jsx │ │ │ ├── events-panel.spec.jsx │ │ │ ├── homework-row.spec.jsx │ │ │ ├── progress-guide.spec.js │ │ │ ├── reading-row.spec.jsx │ │ │ ├── this-week-panel.spec.js │ │ │ └── upcoming-panel.spec.js │ │ ├── surveys.spec.js │ │ ├── task │ │ │ ├── __snapshots__ │ │ │ │ ├── event.spec.js.snap │ │ │ │ ├── exercise-task-header.spec.js.snap │ │ │ │ ├── external.spec.js.snap │ │ │ │ ├── homework.spec.js.snap │ │ │ │ ├── milestones.spec.js.snap │ │ │ │ ├── progress.spec.js.snap │ │ │ │ ├── reading.spec.js.snap │ │ │ │ ├── step-toolbar-toggle.spec.tsx.snap │ │ │ │ └── task-info.spec.js.snap │ │ │ ├── event.spec.js │ │ │ ├── exercise-task-header.spec.js │ │ │ ├── external.spec.js │ │ │ ├── helpers.js │ │ │ ├── homework.spec.js │ │ │ ├── index.spec.js │ │ │ ├── milestones.spec.js │ │ │ ├── progress.spec.js │ │ │ ├── reading-progress.spec.js │ │ │ ├── reading.spec.js │ │ │ ├── response-validation-ux.spec.js │ │ │ ├── step-toolbar-toggle.spec.tsx │ │ │ ├── steps │ │ │ │ ├── __snapshots__ │ │ │ │ │ ├── end.spec.js.snap │ │ │ │ │ ├── exercise-free-response.spec.js.snap │ │ │ │ │ ├── exercise.spec.js.snap │ │ │ │ │ ├── failure.spec.js.snap │ │ │ │ │ ├── html-content.spec.js.snap │ │ │ │ │ ├── instructions.spec.js.snap │ │ │ │ │ ├── nudge-message.spec.js.snap │ │ │ │ │ ├── question-stem.spec.js.snap │ │ │ │ │ ├── reading.spec.js.snap │ │ │ │ │ └── toolbar.spec.js.snap │ │ │ │ ├── end.spec.js │ │ │ │ ├── exercise-free-response.spec.js │ │ │ │ ├── exercise-question.spec.js │ │ │ │ ├── exercise.spec.js │ │ │ │ ├── failure.spec.js │ │ │ │ ├── html-content.spec.js │ │ │ │ ├── index.spec.js │ │ │ │ ├── instructions.spec.js │ │ │ │ ├── nudge-message.spec.js │ │ │ │ ├── question-stem.spec.js │ │ │ │ ├── reading.spec.js │ │ │ │ └── toolbar.spec.js │ │ │ ├── task-info.spec.js │ │ │ ├── ux-task-manipulations.spec.js │ │ │ └── ux.spec.js │ │ └── teacher-dashboard │ │ │ ├── __snapshots__ │ │ │ └── sidebar-toggle.spec.jsx.snap │ │ │ ├── dashboard.spec.js │ │ │ ├── header.spec.jsx │ │ │ ├── helper.spec.js │ │ │ ├── month.spec.js │ │ │ ├── past-assignments.spec.jsx │ │ │ ├── plan-details.spec.jsx │ │ │ ├── sidebar-toggle.spec.jsx │ │ │ └── sidebar.spec.js │ └── task-helpers.spec.js ├── src │ ├── README.md │ ├── TOURS.md │ ├── api.ts │ ├── components │ │ ├── accessibility-statement.jsx │ │ ├── add-edit-question │ │ │ ├── form │ │ │ │ ├── general.js │ │ │ │ ├── index.js │ │ │ │ ├── question.tsx │ │ │ │ ├── shared.js │ │ │ │ ├── tags │ │ │ │ │ ├── constants.js │ │ │ │ │ └── index.js │ │ │ │ └── topic.js │ │ │ ├── index.js │ │ │ ├── modals.js │ │ │ ├── terms-of-use.js │ │ │ └── ux.js │ │ ├── app.jsx │ │ ├── best-practices-tip.js │ │ ├── book-menu │ │ │ ├── menu.jsx │ │ │ ├── toggle.jsx │ │ │ └── ux.js │ │ ├── book-page.tsx │ │ ├── book-part-title.js │ │ ├── branding │ │ │ └── course.jsx │ │ ├── breadcrumb.js │ │ ├── browser-warning-modal │ │ │ └── index.jsx │ │ ├── button-link.js │ │ ├── buttons │ │ │ ├── back-button.js │ │ │ ├── browse-the-book.jsx │ │ │ ├── button-with-tip.js │ │ │ ├── exit-practice.jsx │ │ │ ├── practice-weakest.jsx │ │ │ ├── publish-scores.js │ │ │ ├── reload-page.js │ │ │ ├── save-practice.js │ │ │ └── teacher-become-student.js │ │ ├── cc-student-redirect.jsx │ │ ├── change-student-id.jsx │ │ ├── chapter-section.js │ │ ├── checkbox-input.js │ │ ├── choices-listing.jsx │ │ ├── content-page.js │ │ ├── copy-on-focus-input.jsx │ │ ├── countdown-redirect.js │ │ ├── course-breadcrumb.jsx │ │ ├── course-grouping-label.js │ │ ├── course-modal.js │ │ ├── course-nag.js │ │ ├── course-not-found-warning.js │ │ ├── course-page.jsx │ │ ├── course-period-select.js │ │ ├── course-periods-nav.jsx │ │ ├── course-title-banner.jsx │ │ ├── date-time-input.js │ │ ├── decorators │ │ │ └── window-resize.jsx │ │ ├── dialog.js │ │ ├── dropdown.js │ │ ├── dropped-question.tsx │ │ ├── editor │ │ │ ├── index.js │ │ │ └── runtime.js │ │ ├── enroll.jsx │ │ ├── enroll │ │ │ ├── course-ended.jsx │ │ │ ├── dropped-student.jsx │ │ │ ├── invalid-code.jsx │ │ │ ├── invalid-links-use.jsx │ │ │ ├── invalid-lms-use.jsx │ │ │ ├── invalid-lms.jsx │ │ │ ├── invalid-teacher.jsx │ │ │ ├── select-periods.js │ │ │ ├── student-id.jsx │ │ │ └── unknown-error.jsx │ │ ├── error-monitoring │ │ │ ├── async-load-error.jsx │ │ │ ├── boundary.js │ │ │ ├── handlers.jsx │ │ │ ├── index.jsx │ │ │ └── server-error-message.jsx │ │ ├── exercises │ │ │ ├── cards.jsx │ │ │ ├── details.jsx │ │ │ ├── homework-exercise-filters.js │ │ │ ├── no-exercises-found.jsx │ │ │ ├── preview.jsx │ │ │ └── sectionizer.jsx │ │ ├── ghost-button.tsx │ │ ├── go-to-top.js │ │ ├── grabby-dots.js │ │ ├── header.js │ │ ├── homework-questions.js │ │ ├── icons │ │ │ ├── add.js │ │ │ ├── arrow.js │ │ │ ├── extension.js │ │ │ ├── info.js │ │ │ ├── lock.js │ │ │ ├── pagination.js │ │ │ ├── settings.js │ │ │ └── sort.js │ │ ├── impersonation-warning.js │ │ ├── info-icon-popover.tsx │ │ ├── invalid-page.jsx │ │ ├── late-icon.js │ │ ├── late-points-info.js │ │ ├── link.js │ │ ├── lms-info-card.js │ │ ├── loadable-item.js │ │ ├── match-for-tutor.js │ │ ├── media-preview.tsx │ │ ├── modal-manager.jsx │ │ ├── multi-select.jsx │ │ ├── my-courses │ │ │ ├── course.jsx │ │ │ ├── create-a-course.jsx │ │ │ ├── empty.jsx │ │ │ ├── index.tsx │ │ │ ├── listings.jsx │ │ │ ├── non-allowed-teacher.js │ │ │ └── pending-verification.jsx │ │ ├── name.js │ │ ├── navbar.js │ │ ├── navbar │ │ │ ├── account-link.jsx │ │ │ ├── actions-menu.jsx │ │ │ ├── best-practices-guide.jsx │ │ │ ├── book-links.jsx │ │ │ ├── center-controls.jsx │ │ │ ├── context.js │ │ │ ├── logo.js │ │ │ ├── logout.jsx │ │ │ ├── menus.js │ │ │ ├── nav.js │ │ │ ├── preview-add-course-btn.jsx │ │ │ ├── secondary-toolbar-button.jsx │ │ │ ├── secondary-toolbar.js │ │ │ ├── student-pay-now-btn.jsx │ │ │ ├── student-payment-bar.js │ │ │ ├── support-document-link.jsx │ │ │ ├── support-menu.jsx │ │ │ ├── user-menu.jsx │ │ │ └── username.jsx │ │ ├── new-icon.js │ │ ├── new-tab-link.jsx │ │ ├── no-periods.js │ │ ├── no-students-message.js │ │ ├── notes.js │ │ ├── notes │ │ │ ├── annotate-icon.jsx │ │ │ ├── edit-box.jsx │ │ │ ├── getRangeRect.js │ │ │ ├── highlight-icon.jsx │ │ │ ├── inline-controls.jsx │ │ │ ├── my-highlights-icon.jsx │ │ │ ├── note-card.js │ │ │ ├── sections-filter.jsx │ │ │ ├── sidebar-buttons.jsx │ │ │ ├── summary-page.jsx │ │ │ ├── summary-popup.jsx │ │ │ └── summary-toggle.jsx │ │ ├── number-input.js │ │ ├── obscured-page │ │ │ ├── index.js │ │ │ ├── overlay-registry.js │ │ │ └── overlay.jsx │ │ ├── onboarding │ │ │ ├── course-use-tips.jsx │ │ │ ├── display-preview-message.js │ │ │ ├── expired-preview-warning.jsx │ │ │ ├── free-trial-activated.jsx │ │ │ ├── free-trial-ended.jsx │ │ │ ├── freshly-created-course.jsx │ │ │ ├── make-payment.jsx │ │ │ ├── nag-components.jsx │ │ │ ├── nags.js │ │ │ ├── onboarding-nag.jsx │ │ │ ├── pay-now-or-later.jsx │ │ │ ├── payment-options.tsx │ │ │ ├── payments-disabled.jsx │ │ │ ├── preview-only-warning.jsx │ │ │ ├── redeem-code.tsx │ │ │ ├── second-session-warning.jsx │ │ │ ├── thank-you-for-now.jsx │ │ │ └── ux │ │ │ │ ├── base.ts │ │ │ │ ├── index.ts │ │ │ │ ├── preview.ts │ │ │ │ └── student-course.ts │ │ ├── page-title.js │ │ ├── paging-navigation.jsx │ │ ├── payments │ │ │ ├── manage.jsx │ │ │ ├── modal.jsx │ │ │ ├── panel.jsx │ │ │ └── refund-modal.jsx │ │ ├── plan-stats │ │ │ ├── course-bar.jsx │ │ │ ├── event.jsx │ │ │ ├── index.jsx │ │ │ ├── no-students.jsx │ │ │ ├── performances.jsx │ │ │ └── progress.jsx │ │ ├── preview-side-panel.tsx │ │ ├── question-preview.jsx │ │ ├── radio-input.jsx │ │ ├── related-content-link.js │ │ ├── responsive.js │ │ ├── root.jsx │ │ ├── scroll-spy.js │ │ ├── scroll-tracker.js │ │ ├── search-input.js │ │ ├── sections-chooser.jsx │ │ ├── select.jsx │ │ ├── side-panel.tsx │ │ ├── small-text.js │ │ ├── student-course.spec.js │ │ ├── support-email-link.jsx │ │ ├── tabs.jsx │ │ ├── tag-list.jsx │ │ ├── task-progress.js │ │ ├── teacher-as-student-frame.js │ │ ├── terms-modal.jsx │ │ ├── text.js │ │ ├── time-difference.js │ │ ├── time-input.js │ │ ├── time.js │ │ ├── timezone-modal.js │ │ ├── toasts.ts │ │ ├── toasts │ │ │ ├── add-edit.js │ │ │ ├── course-settings-saved.js │ │ │ ├── lms.jsx │ │ │ ├── reload.jsx │ │ │ ├── scores-published.js │ │ │ └── scores.jsx │ │ ├── top-navbar.js │ │ ├── tours │ │ │ ├── anchor.jsx │ │ │ ├── conductor.jsx │ │ │ ├── custom │ │ │ │ ├── buttons.js │ │ │ │ ├── common.jsx │ │ │ │ ├── drop-any.tsx │ │ │ │ ├── footer.js │ │ │ │ ├── how-to-build-your-reading.jsx │ │ │ │ ├── how-to-use-preview.jsx │ │ │ │ ├── how-to-use-ql.jsx │ │ │ │ ├── index.js │ │ │ │ ├── instructor-welcome-to-tutor.jsx │ │ │ │ ├── new-enrollment-link.jsx │ │ │ │ ├── standard.js │ │ │ │ ├── student-welcome-to-tutor.jsx │ │ │ │ └── tips-now-or-later.jsx │ │ │ ├── region.jsx │ │ │ ├── spotlight.js │ │ │ └── step.js │ │ ├── tri-state-checkbox.jsx │ │ ├── tutor-dialog.js │ │ ├── tutor-errors.js │ │ ├── tutor-input.js │ │ ├── tutor-layout.js │ │ ├── tutor-popover.tsx │ │ ├── tutor-tooltip.tsx │ │ ├── unsaved-state.js │ │ ├── warning-modal.jsx │ │ └── youtube.js │ ├── config.ts │ ├── helpers │ │ ├── analytics.js │ │ ├── async-component.js │ │ ├── backInfo.js │ │ ├── background-wrapper.js │ │ ├── bezier.js │ │ ├── chat.ts │ │ ├── clipboard.js │ │ ├── computed-property.js │ │ ├── conditional-handlers.jsx │ │ ├── contact.js │ │ ├── content.js │ │ ├── course-data.js │ │ ├── course-enrollment.tsx │ │ ├── dates.js │ │ ├── dom.js │ │ ├── download-data.js │ │ ├── durations.js │ │ ├── exercise.js │ │ ├── flux-to-mobx.js │ │ ├── function.js │ │ ├── hooks.ts │ │ ├── images-complete.ts │ │ ├── immutable.js │ │ ├── moment-range.js │ │ ├── notifications.js │ │ ├── number.js │ │ ├── object.js │ │ ├── offering.ts │ │ ├── pardot.ts │ │ ├── payments.tsx │ │ ├── period.ts │ │ ├── production.js │ │ ├── reference-book-base-ux.ts │ │ ├── reload.js │ │ ├── router.js │ │ ├── routes-and-destinations.js │ │ ├── scores.js │ │ ├── scroll-to.js │ │ ├── string.ts │ │ ├── task-plan.js │ │ ├── task.js │ │ ├── time.js │ │ └── webpack-async-loader.js │ ├── models.ts │ ├── models │ │ ├── app.ts │ │ ├── app │ │ │ ├── errors.ts │ │ │ ├── nav-history.ts │ │ │ ├── pulse-insights.ts │ │ │ └── raven.ts │ │ ├── appearance_codes.ts │ │ ├── chapter-section.ts │ │ ├── course.ts │ │ ├── course │ │ │ ├── create.ts │ │ │ ├── information.ts │ │ │ ├── lms.ts │ │ │ ├── offerings.ts │ │ │ ├── offerings │ │ │ │ └── offering.ts │ │ │ ├── pair-to-lms.ts │ │ │ ├── performance.ts │ │ │ ├── period.ts │ │ │ ├── role.ts │ │ │ ├── roster.ts │ │ │ ├── student.ts │ │ │ ├── teacher-profile.ts │ │ │ ├── teacher-student.ts │ │ │ ├── teacher.ts │ │ │ ├── teachers.ts │ │ │ ├── term.ts │ │ │ └── ux.ts │ │ ├── courses-map.ts │ │ ├── ecosystems.ts │ │ ├── ecosystems │ │ │ ├── book.ts │ │ │ └── ecosystem.ts │ │ ├── exercises.ts │ │ ├── exercises │ │ │ ├── exercise.ts │ │ │ └── tag.ts │ │ ├── feature_flags.ts │ │ ├── grading │ │ │ └── templates.ts │ │ ├── job.ts │ │ ├── jobs │ │ │ ├── lms-score-push.ts │ │ │ ├── scores-export.ts │ │ │ └── task-plan-publish.ts │ │ ├── loader.ts │ │ ├── media.ts │ │ ├── notes.ts │ │ ├── notes │ │ │ ├── note.ts │ │ │ └── ux.ts │ │ ├── payment-code.ts │ │ ├── practice-questions.ts │ │ ├── practice-questions │ │ │ └── practice-question.ts │ │ ├── purchases.ts │ │ ├── purchases │ │ │ └── purchase.ts │ │ ├── reference-book.ts │ │ ├── reference-book │ │ │ └── node.ts │ │ ├── related-content.ts │ │ ├── research-surveys.ts │ │ ├── research-surveys │ │ │ └── survey.ts │ │ ├── response_validation.ts │ │ ├── scores.ts │ │ ├── scores │ │ │ ├── heading.ts │ │ │ ├── period.ts │ │ │ ├── student.ts │ │ │ └── task-result.ts │ │ ├── student-tasks.ts │ │ ├── student-tasks │ │ │ ├── info-step.ts │ │ │ ├── step-group.ts │ │ │ ├── step.ts │ │ │ ├── student.ts │ │ │ └── task.ts │ │ ├── subject-order.ts │ │ ├── task-plans │ │ │ ├── student.ts │ │ │ ├── student │ │ │ │ └── task.ts │ │ │ ├── teacher.ts │ │ │ └── teacher │ │ │ │ ├── dropped_question.ts │ │ │ │ ├── grade.ts │ │ │ │ ├── past.ts │ │ │ │ ├── plan.ts │ │ │ │ ├── scores.ts │ │ │ │ ├── stats.ts │ │ │ │ └── tasking.ts │ │ ├── tour.ts │ │ ├── tour │ │ │ ├── actions.ts │ │ │ ├── actions │ │ │ │ ├── base.ts │ │ │ │ ├── hover-exercise.ts │ │ │ │ ├── open-actions-menu.ts │ │ │ │ ├── open-calendar-sidebar.ts │ │ │ │ ├── open-dropdown-menu.ts │ │ │ │ └── open-support-menu.ts │ │ │ ├── context.ts │ │ │ ├── region.ts │ │ │ ├── ride.ts │ │ │ └── step.ts │ │ ├── types.ts │ │ ├── user.ts │ │ ├── user │ │ │ ├── menu.ts │ │ │ ├── terms.ts │ │ │ └── viewed-tour-stat.ts │ │ ├── window-scroll.ts │ │ └── window-size.ts │ ├── primitives.js │ ├── routes.js │ ├── screens │ │ ├── assignment-edit │ │ │ ├── actions.js │ │ │ ├── builder.js │ │ │ ├── chapters.js │ │ │ ├── clone │ │ │ │ ├── editor.js │ │ │ │ ├── index.js │ │ │ │ └── loading.js │ │ │ ├── confirm-template-modal.js │ │ │ ├── controls.js │ │ │ ├── delete-btn.js │ │ │ ├── details-body.js │ │ │ ├── details.js │ │ │ ├── homework │ │ │ │ ├── cards.js │ │ │ │ ├── choose-exercises.jsx │ │ │ │ ├── exercise-controls.jsx │ │ │ │ └── selections-tooltip.jsx │ │ │ ├── index.js │ │ │ ├── points.js │ │ │ ├── preview-tooltip.js │ │ │ ├── questions-overview.js │ │ │ ├── questions.js │ │ │ ├── reorder.js │ │ │ ├── select-sections.js │ │ │ ├── step.js │ │ │ ├── tasking.js │ │ │ ├── unknown.js │ │ │ ├── ux.js │ │ │ └── validations.js │ │ ├── assignment-grade │ │ │ ├── answer.js │ │ │ ├── index.js │ │ │ ├── question.js │ │ │ ├── questions-bar.js │ │ │ ├── styles.scss │ │ │ └── ux.js │ │ ├── assignment-review │ │ │ ├── delete-modal.js │ │ │ ├── details.js │ │ │ ├── drop-question-icon.tsx │ │ │ ├── drop-question-modal.tsx │ │ │ ├── edit-modal.js │ │ │ ├── exercise-type.js │ │ │ ├── external-scores.js │ │ │ ├── grading-block.js │ │ │ ├── grant-extension.js │ │ │ ├── homework-scores.js │ │ │ ├── index.js │ │ │ ├── overview.js │ │ │ ├── reading-scores.js │ │ │ ├── result-tooltip.js │ │ │ ├── scores-data-sorter.js │ │ │ ├── section-link.js │ │ │ ├── styles.scss │ │ │ ├── table-elements.js │ │ │ └── ux.ts │ │ ├── course-roster │ │ │ ├── add-period.jsx │ │ │ ├── add-teacher-link.jsx │ │ │ ├── change-period.jsx │ │ │ ├── delete-period.jsx │ │ │ ├── drop-student.jsx │ │ │ ├── dropped-roster.jsx │ │ │ ├── index.jsx │ │ │ ├── period-roster.jsx │ │ │ ├── remove-teacher.jsx │ │ │ ├── rename-period.jsx │ │ │ ├── student-id-field.jsx │ │ │ ├── student-roster.jsx │ │ │ ├── styles.scss │ │ │ ├── teacher-roster.jsx │ │ │ ├── undrop-student.jsx │ │ │ └── view-archived-periods.jsx │ │ ├── course-settings │ │ │ ├── course-details.js │ │ │ ├── delete-course-button.js │ │ │ ├── index.jsx │ │ │ ├── lms-panel.jsx │ │ │ ├── rename-course.jsx │ │ │ ├── student-access.jsx │ │ │ ├── styles.scss │ │ │ ├── styles │ │ │ │ └── student-access.scss │ │ │ └── timezone.jsx │ │ ├── demo │ │ │ └── editor.js │ │ ├── grading-templates │ │ │ ├── card.js │ │ │ ├── editors.tsx │ │ │ ├── index.js │ │ │ ├── link.js │ │ │ └── modals.jsx │ │ ├── lms-pair │ │ │ ├── app.js │ │ │ ├── create-course-ux.js │ │ │ ├── create-course.jsx │ │ │ ├── existing-course.jsx │ │ │ ├── index.jsx │ │ │ ├── new-or-existing.jsx │ │ │ ├── pair-request.js │ │ │ ├── router.js │ │ │ ├── styles.scss │ │ │ └── ux.js │ │ ├── my-courses │ │ │ ├── dashboard │ │ │ │ ├── add-subject-dropdown.tsx │ │ │ │ ├── create-course.tsx │ │ │ │ ├── delete-offering-modal.tsx │ │ │ │ ├── index.tsx │ │ │ │ ├── offering-block.tsx │ │ │ │ ├── preview-course.tsx │ │ │ │ ├── resource.tsx │ │ │ │ ├── sociology-3e-offering-tooltip.tsx │ │ │ │ ├── sociology-3e-overlay.tsx │ │ │ │ ├── use-displayed-offerings.tsx │ │ │ │ └── view-course.tsx │ │ │ ├── index.tsx │ │ │ ├── new-teacher.tsx │ │ │ └── styles.scss │ │ ├── new-course │ │ │ ├── back-button.jsx │ │ │ ├── build-course.jsx │ │ │ ├── course-clone.jsx │ │ │ ├── course-name.jsx │ │ │ ├── course-numbers.jsx │ │ │ ├── index.jsx │ │ │ ├── new-or-copy.jsx │ │ │ ├── offering-title.jsx │ │ │ ├── offering-unavail.jsx │ │ │ ├── select-course.jsx │ │ │ ├── select-dates.jsx │ │ │ ├── sociology-3e-nudge.tsx │ │ │ ├── steps.js │ │ │ ├── styles.scss │ │ │ ├── ux.js │ │ │ └── wizard.jsx │ │ ├── performance-forecast │ │ │ ├── chapter-section-type.js │ │ │ ├── chapter.js │ │ │ ├── color-key.js │ │ │ ├── guide.js │ │ │ ├── index.js │ │ │ ├── info-link.js │ │ │ ├── practice-button.js │ │ │ ├── practice.js │ │ │ ├── progress-bar.js │ │ │ ├── section.js │ │ │ ├── statistics.js │ │ │ ├── student.js │ │ │ ├── styles.scss │ │ │ ├── styles │ │ │ │ ├── heading.scss │ │ │ │ ├── responsive.scss │ │ │ │ ├── statistics.scss │ │ │ │ ├── teacher-student.scss │ │ │ │ └── teacher.scss │ │ │ ├── teacher-student.js │ │ │ ├── teacher.js │ │ │ ├── weaker-panel.js │ │ │ └── weaker-sections.js │ │ ├── practice-questions │ │ │ ├── delete-modal.js │ │ │ ├── empty-list.js │ │ │ ├── index.js │ │ │ ├── list.js │ │ │ └── ux.js │ │ ├── pre-wrm-scores-report │ │ │ ├── absent-cell.jsx │ │ │ ├── assignment-cell.jsx │ │ │ ├── assignment-header.jsx │ │ │ ├── average-info.jsx │ │ │ ├── correctness-value.jsx │ │ │ ├── dropped-students-caption.js │ │ │ ├── export-controls.jsx │ │ │ ├── export.jsx │ │ │ ├── external-cell.jsx │ │ │ ├── homework-cell.jsx │ │ │ ├── index.jsx │ │ │ ├── late-work.jsx │ │ │ ├── lms-push.jsx │ │ │ ├── name-cell.jsx │ │ │ ├── nav.jsx │ │ │ ├── overall-cell.jsx │ │ │ ├── overall-header.jsx │ │ │ ├── pie-progress.jsx │ │ │ ├── reading-cell.jsx │ │ │ ├── set-weights-modal.jsx │ │ │ ├── sorting-header.jsx │ │ │ ├── student-data-sorter.js │ │ │ ├── styles.scss │ │ │ ├── styles │ │ │ │ ├── controls.scss │ │ │ │ ├── header.scss │ │ │ │ ├── overall-cell.scss │ │ │ │ ├── set-weights-modal.scss │ │ │ │ └── table.scss │ │ │ ├── table-filters.jsx │ │ │ ├── table.jsx │ │ │ ├── ux.js │ │ │ ├── view-weights-modal.jsx │ │ │ └── weights-ux.js │ │ ├── qa-view │ │ │ ├── appearance-selector.js │ │ │ ├── ecosystem-selector.jsx │ │ │ ├── exercise-card.jsx │ │ │ ├── exercises.jsx │ │ │ ├── index.jsx │ │ │ ├── styles.scss │ │ │ ├── ux.js │ │ │ ├── view-toggle.jsx │ │ │ └── view.jsx │ │ ├── question-library │ │ │ ├── dashboard.jsx │ │ │ ├── exercise-controls.jsx │ │ │ ├── exercises-display.jsx │ │ │ ├── exercises-footer.js │ │ │ ├── index.jsx │ │ │ ├── loading-display.jsx │ │ │ ├── sections-chooser.jsx │ │ │ ├── styles.scss │ │ │ └── unsaved-dialog.jsx │ │ ├── reference-book │ │ │ ├── book-title.jsx │ │ │ ├── index.jsx │ │ │ ├── page-navigation.jsx │ │ │ ├── reference-book.jsx │ │ │ ├── section-title.jsx │ │ │ ├── styles.scss │ │ │ ├── styles │ │ │ │ ├── exercise.scss │ │ │ │ └── navbar.scss │ │ │ ├── teacher-content-toggle.jsx │ │ │ └── ux.js │ │ ├── screen-styles.scss │ │ ├── student-dashboard │ │ │ ├── all-events-by-week.jsx │ │ │ ├── cells.js │ │ │ ├── dashboard.jsx │ │ │ ├── empty-panel.jsx │ │ │ ├── event-info-icon.jsx │ │ │ ├── event-row.jsx │ │ │ ├── event-type-icon.js │ │ │ ├── events-panel.jsx │ │ │ ├── hide-button.js │ │ │ ├── index.jsx │ │ │ ├── progress-guide.js │ │ │ ├── status-legend.js │ │ │ ├── styles.scss │ │ │ ├── styles │ │ │ │ ├── card.scss │ │ │ │ ├── dashboard.scss │ │ │ │ ├── dont-forget-panel.scss │ │ │ │ ├── empty-card.scss │ │ │ │ ├── progress-guide.scss │ │ │ │ └── task.scss │ │ │ ├── surveys.jsx │ │ │ ├── task-info.js │ │ │ ├── teacher-pending-load.js │ │ │ ├── this-week-panel.jsx │ │ │ └── upcoming-panel.jsx │ │ ├── student-gradebook │ │ │ ├── index.js │ │ │ ├── table.js │ │ │ └── ux.js │ │ ├── surveys │ │ │ ├── index.jsx │ │ │ └── styles.scss │ │ ├── task │ │ │ ├── back-button.js │ │ │ ├── event.js │ │ │ ├── exercise-task-header.js │ │ │ ├── external.js │ │ │ ├── homework.js │ │ │ ├── index.js │ │ │ ├── milestones.js │ │ │ ├── page-content-ux.js │ │ │ ├── practice.js │ │ │ ├── progress.js │ │ │ ├── reading-milestones-toggle.js │ │ │ ├── reading-navbar.js │ │ │ ├── reading.js │ │ │ ├── response-validation-ux.js │ │ │ ├── step-toolbar-toggle.tsx │ │ │ ├── step │ │ │ │ ├── card.js │ │ │ │ ├── continue-btn.js │ │ │ │ ├── end.js │ │ │ │ ├── exercise-free-response.js │ │ │ │ ├── exercise-question.js │ │ │ │ ├── exercise.js │ │ │ │ ├── failure.js │ │ │ │ ├── html-content.js │ │ │ │ ├── index.js │ │ │ │ ├── instructions.js │ │ │ │ ├── nudge-message.js │ │ │ │ ├── placeholder.js │ │ │ │ ├── question-stem.js │ │ │ │ ├── reading.js │ │ │ │ ├── saved_practice_end.js │ │ │ │ ├── spy-info.js │ │ │ │ ├── toolbar.tsx │ │ │ │ ├── value-props.js │ │ │ │ └── wrq-status.js │ │ │ ├── styles.scss │ │ │ ├── styles │ │ │ │ └── milestones.scss │ │ │ ├── task-info.js │ │ │ ├── ux-task-manipulations.js │ │ │ ├── ux.js │ │ │ └── with-footer.js │ │ ├── teacher-dashboard │ │ │ ├── add-assignment-popup.js │ │ │ ├── add-menu.jsx │ │ │ ├── dashboard.js │ │ │ ├── header.jsx │ │ │ ├── helper.js │ │ │ ├── index.jsx │ │ │ ├── month-title-nav.jsx │ │ │ ├── month.js │ │ │ ├── past-assignments.jsx │ │ │ ├── plan-details.jsx │ │ │ ├── plan-display.jsx │ │ │ ├── plan-label.jsx │ │ │ ├── plan.jsx │ │ │ ├── sidebar-toggle.jsx │ │ │ ├── sidebar.js │ │ │ ├── styles.scss │ │ │ ├── styles │ │ │ │ ├── add.scss │ │ │ │ ├── header.scss │ │ │ │ ├── month.scss │ │ │ │ ├── plan-details.scss │ │ │ │ ├── plan.scss │ │ │ │ └── sidebar.scss │ │ │ └── task-dnd.jsx │ │ └── teacher-gradebook │ │ │ ├── aggregate-result-cell.js │ │ │ ├── average-info-modal.js │ │ │ ├── controls.js │ │ │ ├── export-btn.js │ │ │ ├── index.js │ │ │ ├── lms-push-btn.js │ │ │ ├── min-max-result-cell.js │ │ │ ├── nav.js │ │ │ ├── set-weights-modal.js │ │ │ ├── settings-btn.js │ │ │ ├── sorting-icon.js │ │ │ ├── student-data-sorter.js │ │ │ ├── styles.js │ │ │ ├── styles.scss │ │ │ ├── table.js │ │ │ ├── task-result-cell.js │ │ │ ├── ux.js │ │ │ └── weights-ux.js │ ├── theme.js │ ├── tours │ │ ├── add-homework.json │ │ ├── add-reading.json │ │ ├── analytics-modal.json │ │ ├── course-settings.json │ │ ├── drop-any.json │ │ ├── gradebook.json │ │ ├── index.js │ │ ├── my-courses.json │ │ ├── new-enrollment-link.json │ │ ├── page-tips-reminder.json │ │ ├── performance-forecast.json │ │ ├── question-library.json │ │ ├── review-metrics.json │ │ ├── student-dashboard.json │ │ ├── student-highlighting.json │ │ └── teacher-calendar.json │ └── vendor.ts └── static-loading-screen.js ├── types └── index.d.ts ├── webpack.config.js └── yarn.lock /.codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "header, diff, changes, uncovered, tree" 3 | behavior: default 4 | 5 | # To Turn off comments completely: 6 | # comment: false 7 | -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | screenshots/* filter=lfs diff=lfs merge=lfs -text 2 | screenshots/** filter=lfs diff=lfs merge=lfs -text 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @openstax/tutor-devs 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/template-change.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Change 3 | about: Change issue template 4 | 5 | --- 6 | 7 | ### Description 8 | As a \ I want \ so that \. 9 | 10 | 11 | ### Acceptance Criteria 12 | Give criteria for acceptance. 13 | 14 | 15 | ### Acceptance Tests for testrail 16 | - **title**: 17 | - **categories**: 18 | 19 | > GIVEN something 20 | > AND something else 21 | > WHEN something 22 | > AND something else 23 | > THEN something 24 | > AND something else 25 | 26 | 27 | 28 | ### Tests that will be added or changed 29 | Written explanations of the tests that will have to be added or modified to test this change. 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/template-epic.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Epic 3 | about: Epic template 4 | 5 | --- 6 | ### Description 7 | As a \ I want \ so that \. 8 | 9 | ### Acceptance Criteria 10 | Give criteria for acceptance. 11 | 12 | ### Epic Doc 13 | URL 14 | 15 | **Link the appropriate story issues** 16 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/template-story.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Story 3 | about: Story issue template 4 | 5 | --- 6 | 7 | ### User Story 8 | As a \ I want \ so that \. 9 | 10 | 11 | 12 | ### Acceptance criteria 13 | Describe acceptance criteria 14 | 15 | 16 | ### Acceptance Tests for testrail 17 | - **title**: 18 | - **categories**: 19 | 20 | > GIVEN something 21 | > AND something else 22 | > WHEN something 23 | > AND something else 24 | > THEN something 25 | > AND something else 26 | 27 | 28 | 29 | ### Tests that will be added or changed 30 | Written explanations of the tests that will have to be added or modified to test this story. 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.env 2 | test-results 3 | temp 4 | yarn-error.log 5 | node_modules 6 | bower_components 7 | dist 8 | /cypress/integration 9 | .DS_Store 10 | /.tmp 11 | *.orig 12 | /test-failed-*.png 13 | /coverage 14 | /docs* 15 | archive.tar.gz 16 | *.cjsx.js 17 | /phantomjsdriver.log 18 | */test/some-tests.* 19 | .vscode/ 20 | screenshots/ 21 | cypress/fixtures/ 22 | dump.rdb 23 | -------------------------------------------------------------------------------- /.ignore: -------------------------------------------------------------------------------- 1 | .git 2 | .codecov* 3 | **/api/*.json 4 | *.snap 5 | -------------------------------------------------------------------------------- /.node-inspectorrc: -------------------------------------------------------------------------------- 1 | { 2 | "save-live-edit": true, 3 | "preload": true, 4 | "hidden": ["node_modules/"] 5 | } 6 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | v16.14.0 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Philip Schatz 2 | Amanda Shih 3 | Jing Wang 4 | Darren Mason 5 | Patrick Wolfert 6 | Dantems Soares 7 | Roy E Johnson 8 | Thomas Woodward 9 | Nathan Stitt 10 | Brittany Johnson 11 | Alexander Cam Liu 12 | -------------------------------------------------------------------------------- /Brewfile: -------------------------------------------------------------------------------- 1 | brew "yarn" 2 | brew "shellcheck" 3 | -------------------------------------------------------------------------------- /LICENSING: -------------------------------------------------------------------------------- 1 | OpenStax Tutor welcomes interaction from other entities in the education industry. 2 | If you are a part of an organization that wants to build on the OpenStax Tutor 3 | infrastructure and content but are concerned about the copyleft nature of the 4 | AGPL, please contact us to discuss other open license options. 5 | -------------------------------------------------------------------------------- /bin/checkinputs: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ $# -eq 0 ]; then 4 | echo "No project to build was given. usage:" 5 | echo "$0 " 6 | exit 1 7 | fi -------------------------------------------------------------------------------- /bin/debug: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | bin/checkinputs "$@" 6 | 7 | export OX_PROJECT=$1 8 | echo Building: $OX_PROJECT 9 | export NODE_ENV=debug 10 | 11 | [ -d $OX_PROJECT/dist ] && rm -r $OX_PROJECT/dist 12 | webpack --progress --config webpack.config.js 13 | -------------------------------------------------------------------------------- /bin/health-check: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | 4 | NPM=$(npm bin) 5 | cmd=${1:-run} 6 | args=${@:2} 7 | $NPM/cypress $cmd --config-file configs/test/cypress-healthcheck.json $args 8 | -------------------------------------------------------------------------------- /bin/karma: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | bin/checkinputs "$@" 6 | 7 | export OX_PROJECT=$1 8 | echo Karma: $OX_PROJECT 9 | 10 | karma start configs/test/karma.config.js --auto-watch --no-single-run 11 | -------------------------------------------------------------------------------- /bin/lint: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | EXT=.js,.ts,.jsx,.tsx 6 | 7 | if [ $# -ne 0 ]; then 8 | if [ "$1" == "staged" ]; then 9 | files=`git status --short */{src,specs} | egrep '^[^D|^R].*([j|t]sx?)$' | awk '{ print $2 }'` 10 | if [ ! -z "$files" ]; then 11 | npx eslint $2 $files 12 | fi 13 | elif [ "$1" == "fix" ]; then 14 | npx eslint --ext $EXT {tutor,shared,exercises}/{src,specs} --fix 15 | else 16 | npx eslint --ext $EXT $1/{src,specs} 17 | fi 18 | exit $? 19 | fi 20 | 21 | if [[ ! -z "${OX_PROJECT}" ]]; then 22 | npx eslint --ext $EXT $OX_PROJECT/{src,specs} 23 | exit $? 24 | fi 25 | 26 | npx eslint --ext $EXT {tutor,shared,exercises}/{src,specs} 27 | -------------------------------------------------------------------------------- /bin/screenshot: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export OX_PROJECT=tutor-acceptance 4 | export CACHE=true 5 | 6 | $(npm bin)/jest --runInBand --all --updateSnapshot --config ./configs/test/jest.$OX_PROJECT.js "${@:2}" 7 | -------------------------------------------------------------------------------- /bin/serve: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | bin/checkinputs "$@" 6 | 7 | export OX_PROJECT=$1 8 | echo Serving: $OX_PROJECT 9 | 10 | if [ x$2 == x"standalone" ]; then 11 | node tutor/specs/acceptance/server/index.js 12 | else 13 | node --max_old_space_size=2048 $(npm bin)/webpack-dev-server --debug --progress --config webpack.config.js 14 | fi 15 | -------------------------------------------------------------------------------- /bin/tdd: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | on_termination() { 6 | echo killing webpack $WEBPACK_PID 7 | kill -TERM $WEBPACK_PID 2>/dev/null 8 | } 9 | 10 | bin/checkinputs "$@" 11 | 12 | trap on_termination EXIT 13 | 14 | export OX_PROJECT=$1 15 | echo TDD: $OX_PROJECT 16 | 17 | 18 | webpack-dev-server --config webpack.config.js & 19 | WEBPACK_PID=$! 20 | echo webpack started pid: $WEBPACK_PID 21 | 22 | ./bin/test "$@" 23 | -------------------------------------------------------------------------------- /bin/test-e2e: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | NPM=$(npm bin) 5 | 6 | NOTIFY=8002 7 | URL=http://localhost:$NOTIFY 8 | 9 | # echo $cmd 10 | # echo $args 11 | 12 | echo starting webpack, will wait for $URL 13 | 14 | yarn run tutor:test:e2e:fe --notify-port $NOTIFY & 15 | 16 | PID=$! 17 | 18 | echo started server with PID=$PID 19 | 20 | echo Waiting for server to become available on $URL 21 | $NPM/wait-on $URL 22 | echo FE webpack dev server is now available 23 | 24 | yarn run tutor:test:e2e --reporter=list 25 | 26 | TEST_STATUS=$? 27 | 28 | echo Test run exited with $TEST_STATUS 29 | 30 | kill $PID 31 | 32 | exit $TEST_STATUS 33 | -------------------------------------------------------------------------------- /bin/test-unit: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | args=${@:2} 6 | if [[ ! $args ]]; then 7 | args='--watch' 8 | fi 9 | 10 | if [[ -z "$NODE_OPTIONS" ]]; then 11 | export NODE_OPTIONS="--max-old-space-size=768" 12 | fi 13 | 14 | npx jest --verbose false --config ./configs/test/jest.$1.js $args 15 | -------------------------------------------------------------------------------- /configs/playwright.config.ts: -------------------------------------------------------------------------------- 1 | import { PlaywrightTestConfig, expect } from '@playwright/test' 2 | import { matchers } from 'expect-playwright' 3 | expect.extend(matchers) 4 | 5 | const config: PlaywrightTestConfig = { 6 | testDir: '../tutor/specs/e2e', 7 | testMatch: '*.e2e.ts', 8 | timeout: 1200000, 9 | workers: 2, 10 | forbidOnly: !!process.env.CI, 11 | globalSetup: '../tutor/specs/e2e/setup.ts', 12 | use: { 13 | actionTimeout: 20000, 14 | screenshot: 'only-on-failure', 15 | trace: 'retain-on-failure', 16 | }, 17 | } 18 | 19 | export default config; 20 | -------------------------------------------------------------------------------- /configs/test/cypress-healthcheck.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3001", 3 | "integrationFolder": "tutor/scripts/health-checks", 4 | "screenshotsFolder": "screenshots/health-checks", 5 | "videosFolder": "screenshots/tutor", 6 | "video": false 7 | } 8 | -------------------------------------------------------------------------------- /configs/test/cypress-tutor.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:8110", 3 | "integrationFolder": "tutor/specs/integration", 4 | "screenshotsFolder": "screenshots/tutor", 5 | "videosFolder": "screenshots/tutor" 6 | } 7 | -------------------------------------------------------------------------------- /configs/test/jest.exercises.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageDirectory: '../coverage/exercises/', 3 | rootDir: '../../exercises', 4 | cacheDirectory: '/tmp/exercises', 5 | preset: '../configs/test/jest.config.json', 6 | }; 7 | -------------------------------------------------------------------------------- /configs/test/jest.shared.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageDirectory: '../coverage/shared/', 3 | rootDir: '../../shared', 4 | preset: '../configs/test/jest.config.json', 5 | }; 6 | -------------------------------------------------------------------------------- /configs/test/jest.style-mock.js: -------------------------------------------------------------------------------- 1 | module.exports = 'test-file-stub'; 2 | -------------------------------------------------------------------------------- /configs/test/jest.tutor-acceptance.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageDirectory: '../coverage/tutor/', 3 | rootDir: '../../tutor', 4 | collectCoverage: false, 5 | collectCoverageFrom: [], 6 | testRegex: 'specs/acceptance/.*\.spec\.js$', 7 | testEnvironment: '/specs/acceptance/helpers/jest-environment.js', 8 | setupTestFrameworkScriptFile: '/specs/acceptance/setup.js', 9 | cacheDirectory: '/tmp/tutor', 10 | preset: '../configs/test/jest.config.json', 11 | watchPathIgnorePatterns: ['acceptance/.*\.json$'], 12 | globalSetup: '/specs/acceptance/helpers/global-setup.js', 13 | globalTeardown: '/specs/acceptance/helpers/global-teardown.js', 14 | }; 15 | -------------------------------------------------------------------------------- /configs/test/jest.tutor.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | coverageDirectory: '../coverage/tutor/', 3 | rootDir: '../../tutor', 4 | cacheDirectory: '/tmp/tutor', 5 | testPathIgnorePatterns: [ 6 | '/node_modules/', 7 | '/specs/acceptance', 8 | ], 9 | preset: '../configs/test/jest.config.json', 10 | }; 11 | -------------------------------------------------------------------------------- /docker-compose.exercises.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | services: 3 | build: 4 | command: bash -c "if [ -z \"`ls -A node_modules`\" ]; then yarn; fi && yarn serve exercises" 5 | build: 6 | context: . 7 | target: builder 8 | working_dir: /code 9 | environment: 10 | - "OX_PROJECT_HOST=0.0.0.0" 11 | volumes: 12 | - .:/code:cached 13 | ports: 14 | - "8001:8001" 15 | -------------------------------------------------------------------------------- /docker-compose.override.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | services: 3 | build: 4 | command: bash -c "if [ -z \"`ls -A node_modules`\" ]; then yarn; fi && yarn serve tutor" 5 | build: 6 | context: . 7 | target: builder 8 | working_dir: /code 9 | environment: 10 | - "OX_PROJECT_HOST=0.0.0.0" 11 | volumes: 12 | - .:/code:cached 13 | ports: 14 | - "8000:8000" 15 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | -------------------------------------------------------------------------------- /docker/docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | services: 3 | serve: 4 | image: "openstax/tutor-js:dev" 5 | ports: 6 | - "8000:80" 7 | 8 | -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | server_name localhost; 4 | 5 | location / { 6 | add_header Access-Control-Allow-Origin *; 7 | root /usr/share/nginx/html; 8 | index index.html index.htm; 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /exercises/README.md: -------------------------------------------------------------------------------- 1 | # exercises-js [![Build Status](https://travis-ci.org/openstax/exercises-js.svg?branch=master)](https://travis-ci.org/openstax/exercises-js) [![devDependency Status](https://david-dm.org/openstax/exercises-js/dev-status.svg)](https://david-dm.org/openstax/exercises-js#info=devDependencies) 2 | 3 | # Development Instructions 4 | 5 | ```sh 6 | npm install 7 | npm run tdd 8 | ``` 9 | 10 | To build a dist file: 11 | 12 | ```sh 13 | npm run build exercises 14 | ``` 15 | 16 | To run tests: 17 | 18 | ```sh 19 | npm run test # Or tdd command above 20 | ``` 21 | -------------------------------------------------------------------------------- /exercises/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /exercises/resources/styles/app.scss: -------------------------------------------------------------------------------- 1 | @import './variables/paths'; 2 | @import './vendor-variables'; 3 | @import 'variables'; 4 | @import './mixins/generic'; 5 | @import "./bootstrap"; 6 | 7 | $input-height: 1.5rem; 8 | 9 | @import '../../../shared/resources/styles/main.scss'; 10 | 11 | @import "global/defaults"; 12 | @import "global/widgets"; 13 | @import "./navbar"; 14 | @import "./exercises"; 15 | @import "./vocabulary"; 16 | @import './tags'; 17 | @import './preview'; 18 | @import './print'; 19 | 20 | .exercises-body { 21 | margin-top: 60px; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /exercises/resources/styles/card-background.scss: -------------------------------------------------------------------------------- 1 | @import (reference) './mixins/card'; 2 | 3 | .card.background { 4 | @include x-card(); 5 | 6 | &.mode-view .exercise-background { font-size: 1.5rem; } 7 | &.mode-edit .ql-editor { font-size: 1.5rem; } 8 | 9 | } 10 | -------------------------------------------------------------------------------- /exercises/resources/styles/exercise-preview.scss: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /exercises/resources/styles/global/font-awesome.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstax/tutor-js/7f3135c1c5a803bcd8f74f4c90834e2833d360c6/exercises/resources/styles/global/font-awesome.scss -------------------------------------------------------------------------------- /exercises/resources/styles/global/widgets.scss: -------------------------------------------------------------------------------- 1 | // this file holds small components that have minimal style requirements 2 | // if the styles get much larger, they should be extracted into their own file 3 | 4 | .error-modal { 5 | .response { 6 | max-height: 300px; 7 | overflow: scroll; 8 | border: 1px solid lightgrey; 9 | padding: 1rem; 10 | .html { 11 | .hidden { dispay: none; } 12 | } 13 | } 14 | } 15 | 16 | 17 | .network-activity { 18 | display: flex; 19 | justify-content: center; 20 | .spinner{ 21 | display: flex; 22 | align-items: center; 23 | .message { 24 | font-size: 4rem; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /exercises/resources/styles/mixins/editing-panes.scss: -------------------------------------------------------------------------------- 1 | @mixin editing-panes() { 2 | display: flex; 3 | 4 | .openstax-exercise-preview, 5 | .editing-controls { 6 | flex: 1; 7 | height: 100%; 8 | justify-content: flex-start; 9 | } 10 | .openstax-exercise-preview { 11 | max-width: 800px; 12 | } 13 | 14 | .editing-controls { 15 | margin-right: 1rem; 16 | position: relative; 17 | min-width: 700px; 18 | .add-mpq { 19 | float: right; 20 | } 21 | 22 | } 23 | 24 | .exercise-preview > .openstax-exercise-preview { 25 | height: inherit; 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /exercises/resources/styles/mixins/generic.scss: -------------------------------------------------------------------------------- 1 | // Mostly for the toolbar, but also used to style the quill math popup 2 | @mixin x-user-select($var) { 3 | -moz-user-select: $var; 4 | -webkit-user-select: $var; 5 | } 6 | 7 | @mixin x-btn-dialog() { 8 | margin-bottom: 0; 9 | box-shadow: none; 10 | border: none; 11 | background-color: transparent; 12 | cursor: pointer; 13 | text-transform: uppercase; 14 | color: #4caf50; 15 | &:hover { 16 | color: #66bb6a; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /exercises/resources/styles/navbar.scss: -------------------------------------------------------------------------------- 1 | .navbar { 2 | 3 | .exercise-navbar-controls { 4 | display: flex; 5 | align-items: center; 6 | 7 | > * { 8 | margin-left: 1rem; 9 | } 10 | } 11 | 12 | .dropdown-menu .logout { 13 | input[type=submit] { 14 | border: 0; 15 | background-color: transparent; 16 | width: 100%; 17 | text-align: left; 18 | padding-left: 0; 19 | text-transform: none; 20 | text-align: left; 21 | &:hover { text-decoration: initial; } 22 | } 23 | } 24 | 25 | button + button { 26 | margin-left: 1rem; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /exercises/resources/styles/preview.scss: -------------------------------------------------------------------------------- 1 | .preview-screen { 2 | 3 | padding: 40px; 4 | } 5 | 6 | .openstax-exercise-preview { 7 | .exercise-tag { display: inline; } 8 | } 9 | 10 | .preview-navbar-controls { 11 | display: flex; 12 | justify-content: center; 13 | 14 | .paging-controls { 15 | display: flex; 16 | align-items: center; 17 | 18 | .ex-info { 19 | border-top: 1px solid; 20 | border-bottom: 1px solid; 21 | border-color: $openstax-neutral-medium; 22 | padding: 6px 12px; 23 | min-width: 180px; 24 | display: flex; 25 | align-items: center; 26 | justify-content: center; 27 | } 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /exercises/resources/styles/variables/paths.scss: -------------------------------------------------------------------------------- 1 | $fa-font-path: './fonts'; 2 | 3 | $bootstrap-path: '~bootstrap/less/'; 4 | $widgets-path: '~react-widgets/lib/less/'; 5 | $material-path: '~bootstrap-material-design/less/'; 6 | -------------------------------------------------------------------------------- /exercises/resources/styles/vendor-variables.scss: -------------------------------------------------------------------------------- 1 | //override font awesome path to our own 2 | /* 3 | $fa-font-path: "fonts/"; 4 | 5 | //override materialize icons font face to correct path 6 | @font-face { 7 | font-family: "Material-Design-Icons"; 8 | src:url("fonts/Material-Design-Icons.eot?-g7cqhn"); 9 | src:url("fonts/Material-Design-Icons.eot?#iefix-g7cqhn") format("embedded-opentype"), 10 | url("fonts/Material-Design-Icons.woff?-g7cqhn") format("woff"), 11 | url("fonts/Material-Design-Icons.ttf?-g7cqhn") format("truetype"), 12 | url("fonts/Material-Design-Icons.svg?-g7cqhn#Material-Design-Icons") format("svg"); 13 | font-weight: normal; 14 | font-style: normal; 15 | }*/ 16 | -------------------------------------------------------------------------------- /exercises/resources/styles/vocabulary.scss: -------------------------------------------------------------------------------- 1 | @import "./mixins/editing-panes"; 2 | 3 | .vocabulary-editor { 4 | 5 | @include editing-panes(); 6 | 7 | .distractors { 8 | .heading { 9 | display: flex; 10 | justify-content: space-between; 11 | .label { font-weight: bolder; font-size: 110%; } 12 | .controls i { 13 | cursor: pointer; 14 | font-size: 120%; 15 | } 16 | 17 | } 18 | } 19 | 20 | // remove when vocab id is in spy mode 21 | .vocabulary-hidden-id { 22 | position: fixed; 23 | left: 0; 24 | bottom: 0; 25 | color: lightgrey; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /exercises/specs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'globals': { 3 | 'React': false, 4 | 'beforeAll': false, 5 | 'afterAll': false, 6 | 'beforeEach': false, 7 | 'afterEach': false, 8 | 'describe': false, 9 | 'xdescribe': false, 10 | 'fdescribe': false, 11 | 'test': false, 12 | 'it': false, 13 | 'xit': false, 14 | 'fit': false, 15 | 'expect': false, 16 | 'shallow': false, 17 | 'mount': false, 18 | 'console': false, 19 | 'jest': false, 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /exercises/specs/helpers.js: -------------------------------------------------------------------------------- 1 | export * from 'shared/specs/helpers'; 2 | import { MemoryRouter as Router } from 'react-router-dom'; 3 | import React from 'react'; 4 | import PropTypes from 'prop-types'; 5 | import Factory from './factories' 6 | 7 | export { Router, Factory }; 8 | 9 | function C({ children }) { 10 | return {children}; 11 | } 12 | C.propTypes = { 13 | children: PropTypes.node.isRequired, 14 | }; 15 | export { C }; 16 | -------------------------------------------------------------------------------- /exercises/src/components/exercise-state.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Alert } from 'react-bootstrap'; 3 | 4 | const Loading = () => ( 5 | Loading… 6 | ); 7 | 8 | const NotFound = () => ( 9 | Exercise was not found 10 | ); 11 | 12 | 13 | export { Loading, NotFound }; 14 | -------------------------------------------------------------------------------- /exercises/src/components/search/controls.jsx: -------------------------------------------------------------------------------- 1 | // nothing for now 2 | export default function SearchControls() { 3 | return null; 4 | } 5 | -------------------------------------------------------------------------------- /exercises/src/components/tags/blooms.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import { observer } from 'mobx-react'; 4 | import { range } from 'lodash'; 5 | import Exercise from '../../models/exercises/exercise'; 6 | import SingleDropdown from './single-dropdown'; 7 | 8 | const CHOICES = {}; 9 | for (let i of Array.from(range(1, 7))) { CHOICES[i] = i; } 10 | 11 | function BloomsTag(props) { 12 | return ( 13 | 14 | ); 15 | } 16 | 17 | BloomsTag.propTypes = { 18 | exercise: PropTypes.instanceOf(Exercise).isRequired, 19 | }; 20 | 21 | export default observer(BloomsTag); 22 | -------------------------------------------------------------------------------- /exercises/src/components/tags/dok.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import { observer } from 'mobx-react'; 4 | import { range } from 'lodash'; 5 | import Exercise from '../../models/exercises/exercise'; 6 | import SingleDropdown from './single-dropdown'; 7 | 8 | const CHOICES = {}; 9 | for (let i of Array.from(range(1, 5))) { CHOICES[i] = i; } 10 | 11 | function DokTag(props) { 12 | return ( 13 | 14 | ); 15 | } 16 | 17 | DokTag.propTypes = { 18 | exercise: PropTypes.instanceOf(Exercise).isRequired, 19 | }; 20 | 21 | export default observer(DokTag); 22 | -------------------------------------------------------------------------------- /exercises/src/components/tags/nursingBooks.js: -------------------------------------------------------------------------------- 1 | export const nursingBooks = [ 2 | 'stax-matnewborn', 3 | 'stax-medsurg', 4 | 'stax-nursingfundamentals', 5 | 'stax-nursingskills', 6 | 'stax-nutrition', 7 | 'stax-pharmacology', 8 | 'stax-pophealth', 9 | 'stax-psychnursing', 10 | ]; 11 | -------------------------------------------------------------------------------- /exercises/src/components/tags/time.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import React from 'react'; 3 | import { observer } from 'mobx-react'; 4 | import SingleDropdown from './single-dropdown'; 5 | import Exercise from '../../models/exercises/exercise'; 6 | 7 | const CHOICES = { 8 | 'short': 'Short', 9 | 'medium': 'Medium', 10 | 'long': 'Long', 11 | }; 12 | 13 | function TimeTag(props) { 14 | return ( 15 | 16 | ); 17 | } 18 | 19 | TimeTag.propTypes = { 20 | exercise: PropTypes.instanceOf(Exercise).isRequired, 21 | }; 22 | 23 | export default observer(TimeTag); 24 | -------------------------------------------------------------------------------- /exercises/src/models/exercises/delegation.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, modelize, field } from 'shared/model'; 2 | 3 | 4 | export default 5 | class ExerciseDelegation extends BaseModel { 6 | @field delegator_id = ''; 7 | 8 | @field delegate_id = ''; 9 | @field delegate_type = ''; 10 | 11 | @field can_assign_authorship = true 12 | @field can_assign_copyright = true 13 | @field can_read = true 14 | @field can_update = true 15 | 16 | constructor() { 17 | super() 18 | modelize(this) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /exercises/src/models/exercises/image.ts: -------------------------------------------------------------------------------- 1 | import { modelize, field, BaseModel } from 'shared/model'; 2 | 3 | const STORAGE_PATH = '/rails/active_storage'; 4 | 5 | class Image extends BaseModel { 6 | static directUploadURL = `${STORAGE_PATH}/direct_uploads` 7 | static urlFromBlob(blob: any) { 8 | return `${STORAGE_PATH}/blobs/${blob.signed_id}/${blob.filename}`; 9 | } 10 | 11 | @field signed_id = ''; 12 | @field url = ''; 13 | 14 | constructor() { 15 | super() 16 | modelize(this) 17 | } 18 | } 19 | 20 | export default Image 21 | -------------------------------------------------------------------------------- /exercises/src/models/toasts.ts: -------------------------------------------------------------------------------- 1 | import { currentToasts, setToastHandlers } from 'shared/model/toasts'; 2 | import Published from '../components/exercise/published-toast'; 3 | 4 | setToastHandlers({ 5 | published() { 6 | return Published; 7 | }, 8 | }); 9 | 10 | export default currentToasts; 11 | -------------------------------------------------------------------------------- /exercises/src/models/user.ts: -------------------------------------------------------------------------------- 1 | import { BaseModel, field, modelize, ID, NEW_ID } from 'shared/model'; 2 | 3 | class User extends BaseModel { 4 | 5 | @field id: ID = NEW_ID; 6 | @field username = ''; 7 | @field faculty_status = ''; 8 | @field first_name = ''; 9 | @field full_name = ''; 10 | @field last_name = ''; 11 | @field self_reported_role = ''; 12 | @field support_identifier = ''; 13 | 14 | constructor() { 15 | super() 16 | modelize(this) 17 | } 18 | 19 | bootstrap(data: any) { 20 | Object.assign(this, data); 21 | } 22 | 23 | } 24 | 25 | const currentUser = new User; 26 | 27 | export { User }; 28 | export default currentUser; 29 | -------------------------------------------------------------------------------- /exercises/src/ux.ts: -------------------------------------------------------------------------------- 1 | import { lazyGetter, action } from 'shared/model'; 2 | import qs from 'qs'; 3 | import Search from './models/search'; 4 | 5 | export default class ExerciseUX { 6 | 7 | @lazyGetter get search() { return new Search() } 8 | 9 | @action setSearchDefault(queryString: string) { 10 | const { q: query } = qs.parse(queryString) 11 | if (query && typeof query == 'string') { 12 | this.search.update(query) 13 | this.search.execute() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /script/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." || exit 111 3 | source ./script/bootstrap || exit 111 4 | 5 | # shellcheck disable=SC2068 6 | do_progress_quiet "Building dist" \ 7 | ./bin/build tutor $@ 8 | -------------------------------------------------------------------------------- /script/ci: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | export CI=true 4 | 5 | export NODE_OPTIONS=–max_old_space_size=4096 6 | 7 | cd "$(dirname "$0")/.." || exit 111 8 | 9 | TEST=$1 10 | 11 | if [[ "$TEST" = "lint" ]]; then 12 | ./bin/lint || exit 112 13 | elif [[ "$TEST" = "typecheck" ]]; then 14 | yarn run typecheck 15 | elif [[ "$TEST" = "e2e" ]]; then 16 | ./bin/test-e2e 17 | else 18 | ./bin/test $TEST --all --verbose 19 | fi 20 | -------------------------------------------------------------------------------- /script/generate-tutor-boot-screen: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [ $# -eq 0 ]; then 4 | echo "No destination for screen was given. usage:" 5 | echo "$0 " 6 | exit 1 7 | fi 8 | 9 | DEST=$1/app/views/webview/index.html.erb 10 | 11 | echo '<%-# this file is auto-generated by '$0' in the tutor-js repo -%>' > $DEST 12 | $(npm bin)/babel-node ./tutor/static-loading-screen.js >> $DEST 13 | 14 | echo Wrote loading screen to $DEST 15 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd "$(dirname "$0")/.." || exit 111 3 | source ./script/bootstrap || exit 111 4 | 5 | do_progress "Running Tests" 6 | 7 | # shellcheck disable=SC2068 8 | try ./bin/test tutor $@ 9 | 10 | do_progress_quiet "Linting Shell Scripts" \ 11 | shellcheck ./script/* # ./bin/* 12 | -------------------------------------------------------------------------------- /shared/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstax/tutor-js/7f3135c1c5a803bcd8f74f4c90834e2833d360c6/shared/.DS_Store -------------------------------------------------------------------------------- /shared/api/breadcrumbs/steps.json: -------------------------------------------------------------------------------- 1 | { 2 | "steps": [{ 3 | "is_completed": true, 4 | "answer_id": "3", 5 | "type": "exercise" 6 | }, { 7 | "is_completed": true, 8 | "is_correct": true, 9 | "type": "exercise" 10 | }, { 11 | "type": "exercise" 12 | }, { 13 | "type": "reading" 14 | }] 15 | } -------------------------------------------------------------------------------- /shared/api/exercise/index.js: -------------------------------------------------------------------------------- 1 | import freeResponse from './free-response'; 2 | import multipleChoice from './multiple-choice'; 3 | import review from './review'; 4 | 5 | export default { 6 | 'free-response': freeResponse, 7 | 'multiple-choice': multipleChoice, 8 | 'review': review, 9 | }; 10 | -------------------------------------------------------------------------------- /shared/api/html/data.json: -------------------------------------------------------------------------------- 1 | { 2 | "html": "
" 3 | } -------------------------------------------------------------------------------- /shared/api/notifications.json: -------------------------------------------------------------------------------- 1 | { 2 | "notifications": [ 3 | { 4 | "id": "1", 5 | "message": "Warning: Updates will happen soon! Site will be down." 6 | }, { 7 | "id": "2", 8 | "message": "These are test messages." 9 | } 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /shared/full-build.js: -------------------------------------------------------------------------------- 1 | import exportsToPassOn from './index'; 2 | const mixinsNames = ['ChapterSectionMixin', 'GetPositionMixin', 'ResizeListenerMixin']; 3 | 4 | const componentsToExport = _.omit(exportsToPassOn, mixinsNames); 5 | const mixins = _.pick(exportsToPassOn, mixinsNames); 6 | 7 | const wrapComponent = component => 8 | (DOMNode, props) => React.render(React.createElement(component, props), DOMNode) 9 | ; 10 | 11 | const wrappedExports = _.mapObject(componentsToExport, wrapComponent); 12 | 13 | export default _.extend({}, wrappedExports, mixins); 14 | -------------------------------------------------------------------------------- /shared/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /shared/resources/images/icons/icon-end.svg: -------------------------------------------------------------------------------- 1 | icon-finished -------------------------------------------------------------------------------- /shared/resources/images/icons/icon-video-placeholder.svg: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /shared/resources/images/icons/icon-video.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /shared/resources/images/icons/video-placeholder-icon.svg: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /shared/resources/images/openstax-white-books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstax/tutor-js/7f3135c1c5a803bcd8f74f4c90834e2833d360c6/shared/resources/images/openstax-white-books.png -------------------------------------------------------------------------------- /shared/resources/styles/components/close.scss: -------------------------------------------------------------------------------- 1 | &-close-x { 2 | &::before { 3 | content: "\00d7"; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /shared/resources/styles/components/exercise-badges/index.scss: -------------------------------------------------------------------------------- 1 | &-exercise-badges { 2 | display: flex; 3 | justify-content: flex-end; 4 | align-items: center; 5 | font-size: 1.4rem; 6 | margin: 1rem 0; 7 | color: $openstax-gray; 8 | .icon { 9 | height: 20px; 10 | margin-right: 0.6rem; 11 | } 12 | span { 13 | display: flex; 14 | align-items: center; 15 | & + span { margin-left: 8px; } 16 | } 17 | svg { 18 | margin-right: 0; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /shared/resources/styles/components/exercise/group.scss: -------------------------------------------------------------------------------- 1 | &-step-group { 2 | font-size: 1.6rem; 3 | color: $openstax-neutral; 4 | padding-bottom: 2rem; 5 | padding-top: 0; 6 | text-align: right; 7 | 8 | i.fa { 9 | display: inline-block; 10 | margin: 0 5px; 11 | } 12 | } 13 | 14 | &-step-group-label { 15 | display: inline-block; 16 | margin-left: 0.5rem; 17 | } 18 | -------------------------------------------------------------------------------- /shared/resources/styles/components/html/index.scss: -------------------------------------------------------------------------------- 1 | &-has-html { 2 | @include openstax-tables(); 3 | 4 | .frame-wrapper { 5 | margin: 1rem 0; 6 | } 7 | } 8 | 9 | 10 | -------------------------------------------------------------------------------- /shared/resources/styles/components/ox-colored-stripe.scss: -------------------------------------------------------------------------------- 1 | .ox-colored-stripe { 2 | height: 10px; 3 | display: flex; 4 | // flex values are the relative amount each will take up 5 | .orange { flex: 1; background-color: $openstax-primary; } 6 | .blue { flex: 0.6; background-color: $openstax-tertiary; } 7 | .red { flex: 0.4; background-color: $red; } 8 | .yellow { flex: 0.5; background-color: $openstax-quaternary; } 9 | .teal { flex: 0.4; background-color: $teal; } 10 | } 11 | -------------------------------------------------------------------------------- /shared/resources/styles/components/smart-overflow.scss: -------------------------------------------------------------------------------- 1 | &-smart-overflow { 2 | overflow-y: auto; 3 | overflow-x: hidden; 4 | max-height: none; 5 | } -------------------------------------------------------------------------------- /shared/resources/styles/components/surety-guard.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstax/tutor-js/7f3135c1c5a803bcd8f74f4c90834e2833d360c6/shared/resources/styles/components/surety-guard.scss -------------------------------------------------------------------------------- /shared/resources/styles/demo.scss: -------------------------------------------------------------------------------- 1 | @import "./main"; 2 | 3 | .demos section { 4 | margin-top: 50px; 5 | } 6 | 7 | .demo { 8 | margin: 100px 0; 9 | 10 | } 11 | .notices { 12 | .test-message { 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-between; 16 | width: 500px; 17 | } 18 | } 19 | 20 | .exercisePreview-demo, .exercise-demo, .multipartExercise-demo { 21 | max-width: 800px; 22 | margin: auto; 23 | } 24 | -------------------------------------------------------------------------------- /shared/resources/styles/main.scss: -------------------------------------------------------------------------------- 1 | // External Libraries 2 | // @import './variables/paths'; 3 | // @import './globals/externals'; 4 | 5 | @import './variables/index'; 6 | 7 | @import "~bootstrap/scss/functions"; 8 | @import "~bootstrap/scss/variables"; 9 | @import "~bootstrap/scss/mixins"; 10 | 11 | @import './mixins/index'; 12 | 13 | @import './components/ox-colored-stripe'; 14 | 15 | .openstax { 16 | @import './_components'; 17 | 18 | &-wrapper { 19 | @import './components/breadcrumbs/index'; 20 | @import './components/breadcrumbs/icons'; 21 | @import './components/enroll/new-registration'; 22 | @import './globals/icons'; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /shared/resources/styles/mixins/input.scss: -------------------------------------------------------------------------------- 1 | @mixin openstax-animated-focus-input() { 2 | box-shadow: none; 3 | border: 0; 4 | border-radius: 0; 5 | border-bottom: 1px solid $openstax-neutral-light; 6 | padding-left: 0; 7 | padding-right: 0; 8 | transition: all 0.3s ease; 9 | background: linear-gradient(to bottom, white 96%, $openstax-primary 4%); 10 | background-repeat: no-repeat; 11 | background-position: 50% 0; 12 | background-size: 0px 100%; 13 | &:focus, 14 | &.focus { 15 | outline:none; 16 | outline:none; 17 | background-position: 0 0; 18 | background-size: 100% 100%; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /shared/resources/styles/no-conflict.scss: -------------------------------------------------------------------------------- 1 | @import './variables/paths'; 2 | 3 | @import './variables/index'; 4 | @import './variables/colors'; 5 | @import './variables/openstax-bootstrap'; 6 | @import './mixins/index'; 7 | 8 | @import "#{$bootstrap-path}/mixins"; 9 | @import "#{$fa-path}less/variables.scss"; 10 | @import "#{$fa-path}less/mixins.scss"; 11 | 12 | .openstax { 13 | @import "#{$bootstrap-path}/popovers"; 14 | @import './_components'; 15 | 16 | &-wrapper { 17 | @import './components/breadcrumbs/index'; 18 | @import './components/breadcrumbs/icons'; 19 | @import './components/enroll/new-registration'; 20 | @import './globals/icons'; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /shared/resources/styles/variables/paths.scss: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstax/tutor-js/7f3135c1c5a803bcd8f74f4c90834e2833d360c6/shared/resources/styles/variables/paths.scss -------------------------------------------------------------------------------- /shared/specs/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | globals: { 3 | global: false, 4 | React: false, 5 | beforeAll: false, 6 | afterAll: false, 7 | beforeEach: false, 8 | afterEach: false, 9 | describe: false, 10 | xdescribe: false, 11 | fdescribe: false, 12 | it: false, 13 | xit: false, 14 | fit: false, 15 | expect: false, 16 | shallow: false, 17 | mount: false, 18 | console: false, 19 | jest: false, 20 | fetchMock: false, 21 | }, 22 | }; 23 | -------------------------------------------------------------------------------- /shared/specs/components/__snapshots__/exercise-identifier-link.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Exercise Suggest Correction Link matches snapshot 1`] = ` 4 | 10 | Suggest a correction 11 | 12 | `; 13 | -------------------------------------------------------------------------------- /shared/specs/components/__snapshots__/html.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Arbitrary Html Component renders html 1`] = `"
a test phrase
"`; 4 | 5 | exports[`Arbitrary Html Component renders using span when block is false 1`] = `"a test phrase"`; 6 | -------------------------------------------------------------------------------- /shared/specs/components/__snapshots__/ox-colored-stripe.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`OX Colored Stripe renders and matches snapshot 1`] = ` 4 |
7 |
10 |
13 |
16 |
19 |
22 |
23 | `; 24 | -------------------------------------------------------------------------------- /shared/specs/components/__snapshots__/spy-mode.spec.jsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`SpyMode Wrapper renders a pi symbol 1`] = ` 4 | 17 | `; 18 | -------------------------------------------------------------------------------- /shared/specs/components/buttons/__snapshots__/close-button.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Close Button Component has proper classes 1`] = ` 4 | 22 |
23 | `; 24 | -------------------------------------------------------------------------------- /tutor/specs/components/__snapshots__/navbar.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Main Navbar renders and matches snapshot 1`] = `null`; 4 | -------------------------------------------------------------------------------- /tutor/specs/components/__snapshots__/notes.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Notes renders and matches snapshot 1`] = ` 4 |
7 |
10 |
13 |

14 | hello 15 |

16 |
17 |
18 | `; 19 | -------------------------------------------------------------------------------- /tutor/specs/components/__snapshots__/template-modal.spec.js.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Template Modal renders and matches snapshot 1`] = ` 4 | 7 | `; 8 | -------------------------------------------------------------------------------- /tutor/specs/components/best-practices-tip.spec.js: -------------------------------------------------------------------------------- 1 | import { C } from '../helpers'; 2 | import BestPracticesTip from '../../src/components/best-practices-tip'; 3 | 4 | describe('Course Page', () => { 5 | 6 | it('renders and matches snapshot', () => { 7 | expect.snapshot( 8 | 9 | If you knew better, you’d do betta 10 | 11 | ).toMatchSnapshot(); 12 | 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /tutor/specs/components/buttons/reload-page.spec.js: -------------------------------------------------------------------------------- 1 | import { FakeWindow } from '../../helpers'; 2 | import ReloadButton from '../../../src/components/buttons/reload-page'; 3 | 4 | describe('Reload Button', () => { 5 | let props; 6 | 7 | beforeEach(() => { 8 | props = { 9 | windowImpl: new FakeWindow(), 10 | }; 11 | }); 12 | 13 | it('reloads when clicked', () => { 14 | const rb = mount(); 15 | expect.snapshot(rb.debug()).toMatchSnapshot(); 16 | rb.find('button').simulate('click'); 17 | expect(props.windowImpl.location.reload).toHaveBeenCalledWith(true); 18 | rb.unmount(); 19 | }); 20 | 21 | }); 22 | -------------------------------------------------------------------------------- /tutor/specs/components/checkbox-input.spec.js: -------------------------------------------------------------------------------- 1 | import CheckboxInput from '../../src/components/checkbox-input'; 2 | import { Formik as F } from 'formik'; 3 | 4 | describe('CheckboxInput', () => { 5 | let props; 6 | 7 | beforeEach(() => { 8 | props = { 9 | name: 'input_1', 10 | label: 'Input 1', 11 | id: 'input_1', 12 | }; 13 | }); 14 | 15 | it('matches snapshot', () => { 16 | expect.snapshot().toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tutor/specs/components/content-page.spec.js: -------------------------------------------------------------------------------- 1 | import ContentPage from '../../src/components/content-page'; 2 | 3 | describe('Content Page', () => { 4 | 5 | it('renders and matches snapshot', () => { 6 | expect.snapshot().toMatchSnapshot(); 7 | }); 8 | }); 9 | -------------------------------------------------------------------------------- /tutor/specs/components/course-breadcrumb.spec.jsx: -------------------------------------------------------------------------------- 1 | import { Factory, R } from '../helpers'; 2 | import CourseBreadcrumb from '../../src/components/course-breadcrumb'; 3 | 4 | describe('Course Breadcrumb', () => { 5 | let props; 6 | 7 | beforeEach(function() { 8 | props = { 9 | course: Factory.course(), 10 | currentTitle: 'Current Task', 11 | }; 12 | }); 13 | 14 | it('renders and matches snapshot', () => { 15 | expect.snapshot().toMatchSnapshot(); 16 | }); 17 | }); 18 | -------------------------------------------------------------------------------- /tutor/specs/components/go-to-top.spec.js: -------------------------------------------------------------------------------- 1 | import GoToTop from '../../src/components/go-to-top'; 2 | 3 | describe('GoToTop', () => { 4 | it('matches snapshot', () => { 5 | expect.snapshot().toMatchSnapshot(); 6 | }); 7 | }); 8 | -------------------------------------------------------------------------------- /tutor/specs/components/invalid-page.spec.jsx: -------------------------------------------------------------------------------- 1 | import { C } from '../helpers'; 2 | import InvalidPage from '../../src/components/invalid-page'; 3 | 4 | describe('Invalid Page', () => { 5 | it('renders and matches snapshot', () => { 6 | const wrapper = shallow(); 7 | expect(wrapper).toHaveRendered('OXColoredStripe'); 8 | expect.snapshot().toMatchSnapshot(); 9 | }); 10 | 11 | it('renders a custom message', () => { 12 | const wrapper = shallow(); 13 | expect(wrapper.text()).toContain('Yo'); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /tutor/specs/components/my-courses/pending-verification.spec.jsx: -------------------------------------------------------------------------------- 1 | import { fetchMock } from '../../helpers' 2 | import PendingVerification from '../../../src/components/my-courses/pending-verification'; 3 | import { currentUser } from '../../../src/models'; 4 | 5 | jest.mock('../../../src/helpers/chat'); 6 | 7 | describe('My Courses Pending Verification Component', function() { 8 | 9 | it('renders and matches snapshot', () => { 10 | currentUser.fetch = jest.fn() 11 | fetchMock.mockResponseOnce(JSON.stringify({ test: true })) 12 | expect.snapshot().toMatchSnapshot() 13 | }); 14 | 15 | }); 16 | -------------------------------------------------------------------------------- /tutor/specs/components/navbar/preview-add-course-btn.spec.jsx: -------------------------------------------------------------------------------- 1 | import PreviewAddCourseBtn from '../../../src/components/navbar/preview-add-course-btn'; 2 | import { TourContext } from '../../../src/models'; 3 | import { C, Factory } from '../../helpers'; 4 | 5 | describe('Preview Add Course Button', () => { 6 | it('renders and matches snapshot', () => { 7 | expect.snapshot( 8 | 9 | 10 | ).toMatchSnapshot(); 11 | }); 12 | }); 13 | -------------------------------------------------------------------------------- /tutor/specs/components/onboarding/pay-now-or-later.spec.jsx: -------------------------------------------------------------------------------- 1 | import { C, Factory } from '../../helpers'; 2 | import PayNowOrLater from '../../../src/components/onboarding/pay-now-or-later'; 3 | import { StudentCourseOnboarding as Student } from '../../../src/components/onboarding/ux' 4 | 5 | describe('pay now or later modal', () => { 6 | let props; 7 | beforeEach(() => { 8 | props = { 9 | ux: new Student(Factory.course({ does_cost: true })), 10 | onDismiss: jest.fn(), 11 | }; 12 | }); 13 | 14 | it('renders and matches snapshot', () => { 15 | expect.snapshot().toMatchSnapshot(); 16 | }); 17 | 18 | }); 19 | -------------------------------------------------------------------------------- /tutor/specs/components/onboarding/redeem-code.spec.jsx: -------------------------------------------------------------------------------- 1 | import { C } from '../../helpers'; 2 | import RedeemCode from '../../../src/components/onboarding/redeem-code'; 3 | import { StudentCourseOnboarding as CourseUX } from '../../../src/components/onboarding/ux/student-course' 4 | 5 | describe('Redeem Code', () => { 6 | 7 | let ux; 8 | 9 | beforeEach(() => { 10 | ux = new CourseUX({}); 11 | }); 12 | 13 | it('renders and matches snapshot', () => { 14 | expect.snapshot( 15 | 16 | ).toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tutor/specs/components/radio-input.spec.jsx: -------------------------------------------------------------------------------- 1 | import RadioInput from '../../src/components/radio-input'; 2 | import { Formik as F } from 'formik'; 3 | 4 | describe('RadioInput', () => { 5 | let props; 6 | 7 | beforeEach(() => { 8 | props = { 9 | name: 'input_1', 10 | label: 'Input 1', 11 | id: 'input_1', 12 | }; 13 | }); 14 | 15 | it('matches snapshot', () => { 16 | expect.snapshot().toMatchSnapshot(); 17 | }); 18 | }); 19 | -------------------------------------------------------------------------------- /tutor/specs/components/select.spec.jsx: -------------------------------------------------------------------------------- 1 | import Select from '../../src/components/select'; 2 | import { Formik as F } from 'formik'; 3 | 4 | describe('Select', () => { 5 | let props; 6 | 7 | beforeEach(() => { 8 | props = { 9 | name: 'select1', 10 | }; 11 | }); 12 | 13 | it('matches snapshot', () => { 14 | expect.snapshot(