├── test
├── mocha.opts
├── four-oh-fours.js
└── routes
│ └── user.js
├── public
├── stylesheets
│ ├── .gitignore
│ ├── standalone.scss
│ ├── print.scss
│ ├── guppy-fixes.scss
│ ├── images.scss
│ ├── user.scss
│ ├── byline.scss
│ ├── tabular.scss
│ ├── footer.scss
│ ├── push-down-footer.scss
│ ├── deprecated
│ │ └── clock.scss
│ ├── dialogue.scss
│ ├── latex.scss
│ ├── abstract.scss
│ ├── hints.scss
│ ├── pulsate.scss
│ ├── flash.scss
│ ├── chat.scss
│ ├── mathjax.scss
│ ├── certificate.scss
│ ├── activity.scss
│ ├── fonts.scss
│ ├── carousel.scss
│ ├── syntaxHighlighter.scss
│ └── xourse.scss
├── images
│ ├── stickers
│ │ ├── tikzLogo.tex
│ │ └── criticalItem.tex
│ ├── osu
│ │ ├── favicon.ico
│ │ ├── osu_name.png
│ │ ├── resp-help.png
│ │ ├── resp-map.png
│ │ ├── osu_name@2x.png
│ │ ├── resp-map@2x.png
│ │ ├── resp-search.png
│ │ ├── bg-navbar_red.jpg
│ │ ├── button-search.png
│ │ ├── resp-help@2x.png
│ │ ├── resp-search@2x.png
│ │ ├── resp-webmail.png
│ │ ├── white
│ │ │ ├── osu_name.png
│ │ │ ├── resp-map.png
│ │ │ ├── resp-help.png
│ │ │ ├── bg-navbar_red.png
│ │ │ ├── osu_name@2x.png
│ │ │ ├── resp-help@2x.png
│ │ │ ├── resp-map@2x.png
│ │ │ ├── resp-search.png
│ │ │ ├── resp-webmail.png
│ │ │ ├── resp-findpeople.png
│ │ │ ├── resp-search@2x.png
│ │ │ ├── resp-webmail@2x.png
│ │ │ ├── resp-buckeyelink.png
│ │ │ ├── resp-findpeople@2x.png
│ │ │ ├── resp-buckeyelink@2x.png
│ │ │ ├── resp-buckeyelink-network.png
│ │ │ └── resp-buckeyelink-network@2x.png
│ │ ├── apple-touch-icon.png
│ │ ├── resp-buckeyelink.png
│ │ ├── resp-findpeople.png
│ │ ├── resp-webmail@2x.png
│ │ ├── resp-buckeyelink@2x.png
│ │ ├── resp-findpeople@2x.png
│ │ ├── osu-web-footer-wordmark.png
│ │ ├── osu-web-header-vert2-rev.png
│ │ ├── resp-buckeyelink-network.png
│ │ ├── osu-web-footer-wordmark-rev.png
│ │ └── resp-buckeyelink-network@2x.png
│ ├── photos
│ │ ├── workshop.jpg
│ │ ├── san-diego.jpg
│ │ ├── tablet-and-book.jpg
│ │ └── calculus-textbooks.jpg
│ ├── icons
│ │ └── favicon
│ │ │ ├── icon.xcf
│ │ │ ├── favicon.ico
│ │ │ ├── .gitignore
│ │ │ └── Makefile
│ ├── colorado-state
│ │ └── logo.png
│ ├── ascii
│ │ └── ximera.ascii.txt
│ └── logo
│ │ └── tikzLogo.tex
├── fonts
│ └── stix
│ │ ├── stixgeneral-webfont.eot
│ │ ├── stixgeneral-webfont.ttf
│ │ ├── stixgeneral-webfont.woff
│ │ ├── stixgeneralbol-webfont.eot
│ │ ├── stixgeneralbol-webfont.ttf
│ │ ├── stixgeneralbol-webfont.woff
│ │ ├── stixgeneralbolita-webfont.eot
│ │ ├── stixgeneralbolita-webfont.ttf
│ │ ├── stixgeneralbolita-webfont.woff
│ │ ├── stixgeneralitalic-webfont.eot
│ │ ├── stixgeneralitalic-webfont.ttf
│ │ └── stixgeneralitalic-webfont.woff
└── javascripts
│ ├── sticky-scroll.js
│ ├── cache-bust.js
│ ├── image-environment.js
│ ├── rowclick.js
│ ├── desmos.js
│ ├── annotator.js
│ ├── brushes
│ └── shBrushLatex.js
│ ├── version.js
│ ├── instructor.js
│ ├── mathjax.js
│ ├── feedback.js
│ ├── hint.js
│ ├── xourse.js
│ ├── foldable.js
│ ├── gradebook.js
│ ├── shuffle.js
│ ├── activity-card.js
│ ├── users.js
│ ├── popover.js
│ ├── coding.js
│ ├── index.js
│ ├── activity.js
│ ├── math-palette.js
│ ├── pencil.js
│ ├── free-response.js
│ ├── javascript.js
│ ├── validator.js
│ ├── interactives.js
│ └── sw.js
├── blog
├── XimeraGraphic.png
├── shuttleworth.markdown
├── xw1Day3.markdown
├── xw1Day2.markdown
├── xw1Day1.markdown
├── cpc.markdown
└── bigdata.markdown
├── views
├── layouts
│ ├── navbar
│ │ ├── due-date.pug
│ │ ├── palette.pug
│ │ ├── another.pug
│ │ ├── erase.pug
│ │ ├── search.pug
│ │ ├── progress-bar.pug
│ │ ├── update.pug
│ │ ├── statistics.pug
│ │ ├── edit.pug
│ │ ├── brand.pug
│ │ ├── save.pug
│ │ ├── index.pug
│ │ ├── get-help.pug
│ │ └── login.pug
│ ├── includes
│ │ ├── helpers.pug
│ │ └── osu.pug
│ └── footer
│ │ └── index.pug
├── fail.pug
├── watch.pug
├── links.pug
├── 500.pug
├── sample.tex
├── page
│ ├── global-preamble.pug
│ ├── navigation.pug
│ ├── next-and-previous.pug
│ └── breadcrumbs.pug
├── 404.pug
├── source.pug
├── xourses
│ ├── index.pug
│ └── view.pug
├── modals
│ ├── reset.pug
│ ├── update-xourse.pug
│ ├── confirm-deletion.pug
│ ├── update.pug
│ └── guppymath.pug
├── lti
│ ├── passback.pug
│ └── config.pug
├── install.sh
├── certificate
│ ├── xourse.pug
│ └── view.pug
├── page.pug
├── user
│ └── index.pug
├── activity-card.pug
└── instructors.pug
├── .travis.yml
├── remember.js
├── .gitignore
├── public_key.pem
├── routes
├── xourses.js
├── statistics.js
├── etag.js
├── cachify.js
├── hashcash.js
├── supervising.js
├── instructors.js
├── poetry.js
└── course.js
├── deploy.sh
├── branding.js
├── login
├── guests.js
└── passport-lti.js
├── map-reduce
├── progress-reports.js
├── map-reduce.js
└── map-reduce-incremental.js
├── summarize
├── read-lrs.js
└── summarize.js
├── documentation
└── interactives.md
├── nginx.conf
└── config.js
/test/mocha.opts:
--------------------------------------------------------------------------------
1 | --recursive
--------------------------------------------------------------------------------
/public/stylesheets/.gitignore:
--------------------------------------------------------------------------------
1 | *.css
2 |
--------------------------------------------------------------------------------
/public/images/stickers/tikzLogo.tex:
--------------------------------------------------------------------------------
1 | ../logo/tikzLogo.tex
--------------------------------------------------------------------------------
/blog/XimeraGraphic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/blog/XimeraGraphic.png
--------------------------------------------------------------------------------
/public/stylesheets/standalone.scss:
--------------------------------------------------------------------------------
1 | @import "./base.scss";
2 |
3 | body {
4 | margin: 10pt;
5 | }
6 |
--------------------------------------------------------------------------------
/public/stylesheets/print.scss:
--------------------------------------------------------------------------------
1 | @media print {
2 | .chat {
3 | display: none !important;
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/public/images/osu/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/favicon.ico
--------------------------------------------------------------------------------
/public/images/osu/osu_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/osu_name.png
--------------------------------------------------------------------------------
/public/images/osu/resp-help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-help.png
--------------------------------------------------------------------------------
/public/images/osu/resp-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-map.png
--------------------------------------------------------------------------------
/public/images/osu/osu_name@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/osu_name@2x.png
--------------------------------------------------------------------------------
/public/images/osu/resp-map@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-map@2x.png
--------------------------------------------------------------------------------
/public/images/osu/resp-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-search.png
--------------------------------------------------------------------------------
/public/images/photos/workshop.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/photos/workshop.jpg
--------------------------------------------------------------------------------
/public/images/icons/favicon/icon.xcf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/icons/favicon/icon.xcf
--------------------------------------------------------------------------------
/public/images/osu/bg-navbar_red.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/bg-navbar_red.jpg
--------------------------------------------------------------------------------
/public/images/osu/button-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/button-search.png
--------------------------------------------------------------------------------
/public/images/osu/resp-help@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-help@2x.png
--------------------------------------------------------------------------------
/public/images/osu/resp-search@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-search@2x.png
--------------------------------------------------------------------------------
/public/images/osu/resp-webmail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-webmail.png
--------------------------------------------------------------------------------
/public/images/osu/white/osu_name.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/osu_name.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-map.png
--------------------------------------------------------------------------------
/public/images/photos/san-diego.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/photos/san-diego.jpg
--------------------------------------------------------------------------------
/public/images/colorado-state/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/colorado-state/logo.png
--------------------------------------------------------------------------------
/public/images/osu/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/apple-touch-icon.png
--------------------------------------------------------------------------------
/public/images/osu/resp-buckeyelink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-buckeyelink.png
--------------------------------------------------------------------------------
/public/images/osu/resp-findpeople.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-findpeople.png
--------------------------------------------------------------------------------
/public/images/osu/resp-webmail@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-webmail@2x.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-help.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-help.png
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneral-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneral-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneral-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneral-webfont.ttf
--------------------------------------------------------------------------------
/public/images/icons/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/icons/favicon/favicon.ico
--------------------------------------------------------------------------------
/public/images/osu/resp-buckeyelink@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-buckeyelink@2x.png
--------------------------------------------------------------------------------
/public/images/osu/resp-findpeople@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-findpeople@2x.png
--------------------------------------------------------------------------------
/public/images/osu/white/bg-navbar_red.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/bg-navbar_red.png
--------------------------------------------------------------------------------
/public/images/osu/white/osu_name@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/osu_name@2x.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-help@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-help@2x.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-map@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-map@2x.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-search.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-webmail.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-webmail.png
--------------------------------------------------------------------------------
/public/images/photos/tablet-and-book.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/photos/tablet-and-book.jpg
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneral-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneral-webfont.woff
--------------------------------------------------------------------------------
/public/images/osu/white/resp-findpeople.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-findpeople.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-search@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-search@2x.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-webmail@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-webmail@2x.png
--------------------------------------------------------------------------------
/public/images/photos/calculus-textbooks.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/photos/calculus-textbooks.jpg
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralbol-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralbol-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralbol-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralbol-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralbol-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralbol-webfont.woff
--------------------------------------------------------------------------------
/public/images/osu/osu-web-footer-wordmark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/osu-web-footer-wordmark.png
--------------------------------------------------------------------------------
/public/images/osu/osu-web-header-vert2-rev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/osu-web-header-vert2-rev.png
--------------------------------------------------------------------------------
/public/images/osu/resp-buckeyelink-network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-buckeyelink-network.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-buckeyelink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-buckeyelink.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-findpeople@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-findpeople@2x.png
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralbolita-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralbolita-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralbolita-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralbolita-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralbolita-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralbolita-webfont.woff
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralitalic-webfont.eot:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralitalic-webfont.eot
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralitalic-webfont.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralitalic-webfont.ttf
--------------------------------------------------------------------------------
/public/fonts/stix/stixgeneralitalic-webfont.woff:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/fonts/stix/stixgeneralitalic-webfont.woff
--------------------------------------------------------------------------------
/public/images/osu/white/resp-buckeyelink@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-buckeyelink@2x.png
--------------------------------------------------------------------------------
/public/images/icons/favicon/.gitignore:
--------------------------------------------------------------------------------
1 | favicon-114x114.png
2 | favicon-54x54.png
3 | favicon-64x64.png
4 | favicon-72x72.png
5 | favicon.ico
6 | icon.png
7 |
--------------------------------------------------------------------------------
/public/images/osu/osu-web-footer-wordmark-rev.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/osu-web-footer-wordmark-rev.png
--------------------------------------------------------------------------------
/public/images/osu/resp-buckeyelink-network@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/resp-buckeyelink-network@2x.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-buckeyelink-network.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-buckeyelink-network.png
--------------------------------------------------------------------------------
/public/images/osu/white/resp-buckeyelink-network@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/XimeraProject/server/HEAD/public/images/osu/white/resp-buckeyelink-network@2x.png
--------------------------------------------------------------------------------
/views/layouts/navbar/due-date.pug:
--------------------------------------------------------------------------------
1 | span.navbar-text#dueDate.mr-1(style="display: none;",data-toggle="tooltip",data-placement="bottom")
2 | i.fa.fa-clock-o.mr-1
3 | span#dueDateCountdown
4 |
--------------------------------------------------------------------------------
/views/layouts/navbar/palette.pug:
--------------------------------------------------------------------------------
1 | button.btn.btn-info.mx-1#math-edit-button(role="button",style="display: none;")
2 | |
3 | span.hidden-md-down Math Editor
4 |
--------------------------------------------------------------------------------
/views/layouts/navbar/another.pug:
--------------------------------------------------------------------------------
1 | button.btn.btn-success.mx-1#show-me-another-button(role="button", style="display: none")
2 | |
3 | span.hidden-md-down Another
4 |
--------------------------------------------------------------------------------
/views/layouts/navbar/erase.pug:
--------------------------------------------------------------------------------
1 | if activity
2 | button.btn.mx-1.btn-danger(type="button",data-toggle="modal", data-target="#resetWarningModal")
3 | |
4 | span.hidden-md-down Erase
5 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | services: mongodb
3 | node_js:
4 | - 'node'
5 | cache:
6 | directories:
7 | - node_modules
8 | before_script:
9 | - npm install -g gulp-cli
10 | - gulp
11 | script: mocha
12 |
--------------------------------------------------------------------------------
/views/layouts/navbar/search.pug:
--------------------------------------------------------------------------------
1 | form.form-inline.mr-1
2 | div.input-group
3 | input.form-control(id="xourse-search",type="search",placeholder="Search for...")
4 | span.input-group-addon
5 | i.fa.fa-search
6 |
--------------------------------------------------------------------------------
/views/layouts/navbar/progress-bar.pug:
--------------------------------------------------------------------------------
1 | div.progress.completion-meter.mx-1.h-100.align-self-center(style="width: 10em;")
2 | div.progress-bar.bg-success.h-100(role="progressbar",aria-valuenow="0",aria-valuemin="0",aria-valuemax="100")
3 |
--------------------------------------------------------------------------------
/views/layouts/navbar/update.pug:
--------------------------------------------------------------------------------
1 | button.btn.btn-warning.pulsate.mx-1#pageUpdate(type="button",data-toggle="modal", data-target="#updateWarningModal", style="display: none;")
2 | |
3 | span.hidden-md-down Update
4 |
--------------------------------------------------------------------------------
/public/stylesheets/guppy-fixes.scss:
--------------------------------------------------------------------------------
1 | // Make the guppy math input window as wide as the screen
2 |
3 | #guppymathModal {
4 | padding-right: 0;
5 | }
6 |
7 | #guppymathModal .modal-dialog {
8 | padding: 6pt;
9 | max-width: 100%;
10 | }
11 |
--------------------------------------------------------------------------------
/views/fail.pug:
--------------------------------------------------------------------------------
1 | extends layouts/main
2 |
3 | block title
4 | | #{title}
5 |
6 | block content
7 | .container
8 | h1.text-center I am so sorry.
9 | h3.text-center An internal server error has occurred.
10 | p.text-center #{message}
11 |
--------------------------------------------------------------------------------
/public/stylesheets/images.scss:
--------------------------------------------------------------------------------
1 | .image-environment {
2 | background-color: white;
3 | text-align: center;
4 | }
5 |
6 | /* BADBAD: maybe this should be controlled with tex4ht somehow */
7 | .image-environment img {
8 | width: 75%;
9 | }
10 |
11 |
--------------------------------------------------------------------------------
/public/stylesheets/user.scss:
--------------------------------------------------------------------------------
1 | #api-secret {
2 | text-overflow: ellipsis;
3 | overflow: hidden;
4 | white-space: nowrap;
5 | }
6 |
7 | #api-key {
8 | text-overflow: ellipsis;
9 | overflow: hidden;
10 | white-space: nowrap;
11 | }
12 |
--------------------------------------------------------------------------------
/views/watch.pug:
--------------------------------------------------------------------------------
1 | extends layouts/main
2 |
3 | block title
4 | | Supervise
5 |
6 | block content
7 | div.container.mt-5
8 | .row.p-2
9 | h1(role="banner") Supervise
10 | .container.ml-5.p-5
11 | main
12 | ul.list-unstyled#supervision
13 |
--------------------------------------------------------------------------------
/public/stylesheets/byline.scss:
--------------------------------------------------------------------------------
1 | /* The styling for the abstract */
2 | #byline:not(:empty):before {
3 | content:"By ";
4 | font-style:normal;
5 | display: inline;
6 | }
7 |
8 | #byline {
9 | color: #AAA;
10 | }
11 |
12 | #byline:empty {
13 | display: none;
14 | }
15 |
--------------------------------------------------------------------------------
/remember.js:
--------------------------------------------------------------------------------
1 | module.exports = function (req, res, next) {
2 | // BADBAD: Does this cause terrible race conditions? Why would it?
3 | if (req.user) {
4 | req.user.lastUrlVisited = req.url;
5 | req.user.lastSeen = new Date();
6 | req.user.save();
7 | }
8 | next();
9 | };
10 |
11 |
--------------------------------------------------------------------------------
/views/layouts/navbar/statistics.pug:
--------------------------------------------------------------------------------
1 | if activity
2 | button.btn.mx-1.btn-info#instructor-view-statistics(type="button", data-activity-url=repositoryName + '/' + activity.path, data-activity-hash=activity.hash,style="display: none;")
3 | |
4 | span.hidden-md-down Statistics
5 |
--------------------------------------------------------------------------------
/public/stylesheets/tabular.scss:
--------------------------------------------------------------------------------
1 | table.tabular {
2 | @extend .table;
3 | }
4 |
5 | table.tabular th, table.tabular td {
6 | border-top: none;
7 | }
8 |
9 | tr.hline hr {
10 | display: none;
11 | }
12 |
13 | tr.hline {
14 | border-top: 1px solid black;
15 | }
16 |
17 | table.tabular td {
18 | padding: 0;
19 | }
20 |
--------------------------------------------------------------------------------
/public/javascripts/sticky-scroll.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 | var bootstrap = require('bootstrap');
3 |
4 | $(document).ready(function() {
5 | var offset = $("#scroller-anchor").offset();
6 |
7 | if (offset) {
8 | $('#scroller').affix({
9 | offset: {
10 | top: offset.top
11 | }
12 | });
13 | }
14 | });
15 |
16 |
--------------------------------------------------------------------------------
/views/links.pug:
--------------------------------------------------------------------------------
1 | mixin courseActivityLink(course,activity)
2 | if activity
3 | a(href=course.activityURL(activity))
4 | block
5 | else
6 | a(href="#")
7 | block
8 |
9 |
10 | mixin courseLink(course)
11 | if course
12 | a(href=`/course/${course.slug}/`)
13 | block
14 | else
15 | a(href="#")
16 | block
17 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | lib-cov
2 | ecosystem.config.js
3 | private_key.pem
4 | *.seed
5 | *.log
6 | *.csv
7 | *.dat
8 | *.out
9 | *.pid
10 | *.gz
11 |
12 | \#*#
13 |
14 | pids
15 | logs
16 | results
17 |
18 | *~
19 |
20 | public/stylesheets/base.css
21 |
22 | npm-debug.log
23 | node_modules
24 | components
25 | bower_components
26 |
27 | environment.sh
28 | .env
29 |
--------------------------------------------------------------------------------
/public/stylesheets/footer.scss:
--------------------------------------------------------------------------------
1 | footer {
2 | margin-top: 5ex;
3 | padding-top: 5ex;
4 | }
5 |
6 | footer a {
7 | color: white;
8 | }
9 |
10 | footer ul {
11 | padding: 0;
12 | }
13 |
14 | footer ul a {
15 | display: block;
16 | }
17 |
18 | footer a:hover {
19 | color: #DDD;
20 | }
21 |
22 | footer p {
23 | margin-bottom: 0.25ex;
24 | }
25 |
26 |
--------------------------------------------------------------------------------
/views/500.pug:
--------------------------------------------------------------------------------
1 | extends layouts/main
2 |
3 | block title
4 | | 500
5 |
6 | block content
7 | .container
8 | .row.d-flex.align-items-center.justify-content-around
9 | div.mt-5
10 | h1(style="font-size: 100pt;") 500
11 | h1.text-muted I am so sorry.
12 | div.mt-5
13 | h3 An internal server error has occurred.
14 | p #{message}
15 |
--------------------------------------------------------------------------------
/views/layouts/navbar/edit.pug:
--------------------------------------------------------------------------------
1 | if activity && activity.path && repositoryMetadata && repositoryMetadata.github
2 | button.btn.btn-default.mx-1(role="button",onclick=`location.href = \"https://github.com/${repositoryMetadata.github.owner}/${repositoryMetadata.github.repository}/edit/master/${activity.path}.tex\";`)
3 | |
4 | span.hidden-md-down Edit
5 |
--------------------------------------------------------------------------------
/public/stylesheets/push-down-footer.scss:
--------------------------------------------------------------------------------
1 | html {
2 | position: relative;
3 | min-height: 100%;
4 | }
5 | body {
6 | /* Margin bottom by footer height */
7 | margin-bottom: 26rem;
8 | }
9 | footer {
10 | position: absolute;
11 | bottom: 0;
12 | width: 100%;
13 | /* Set the fixed height of the footer here */
14 | height: 23rem;
15 | background-color: #f5f5f5;
16 | }
17 |
18 |
--------------------------------------------------------------------------------
/public/javascripts/cache-bust.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 |
3 | $(document).ready(function() {
4 | // Refresh to bust cache
5 | if (window.location.search.match(/^\?/)) {
6 | if ((window.history) && (window.history.pushState)) {
7 | window.history.pushState( {}, document.title, window.location.pathname );
8 | window.location.reload(true);
9 | }
10 | }
11 | });
12 |
13 |
--------------------------------------------------------------------------------
/views/layouts/navbar/brand.pug:
--------------------------------------------------------------------------------
1 | if (atColoradoState)
2 | a.navbar-brand.hidden-sm-down(href="/math160fa17")
3 | img(src=versionPath("/public/images/logo/logo.svg"),style="max-height: 1rem; opacity: 0.5; vertical-align: baseline;")
4 | else
5 | a.navbar-brand.hidden-sm-down(href="/")
6 | img(src=versionPath("/public/images/logo/logo.svg"),style="max-height: 1rem; opacity: 0.5; vertical-align: baseline;")
7 |
--------------------------------------------------------------------------------
/public/stylesheets/deprecated/clock.scss:
--------------------------------------------------------------------------------
1 | #clock {
2 | display: none;
3 | }
4 |
5 | #clock .clock-display {
6 | color: gray;
7 | }
8 |
9 | #clock .clock-second, #clock .clock-minute {
10 | color: black;
11 | display: inline-block;
12 | width: 2ch;
13 | }
14 |
15 | #clock .clock-second {
16 | text-align: left;
17 | }
18 |
19 | #clock .clock-minute {
20 | text-align: right;
21 | }
22 |
--------------------------------------------------------------------------------
/views/sample.tex:
--------------------------------------------------------------------------------
1 | \begin{example}
2 | Use a linear approximation of $f(x)=\sin(x)$
3 | at $x=0$ to approximate $\sin(0.3)$.
4 |
5 | \begin{explanation}
6 | To start, write
7 | \[
8 | \frac{d}{dx} f(x) = \answer{\cos(x)},
9 | \]
10 | so our linear approximation is
11 | \begin{align*}
12 | \l(x) &= \answer{\cos(0)}\cdot(x-0) + 0\\
13 | &= x.
14 | \end{align*}
15 | \end{explanation}
16 | \end{example}
17 |
--------------------------------------------------------------------------------
/public/images/ascii/ximera.ascii.txt:
--------------------------------------------------------------------------------
1 | ▀██▄ ▄██▀ ██ █████ █████ ▄███████████████████▄ ███
2 | ▀██▄██▀ ██▐██ ▐██ ██▌ ██▌██ ██▌ ██▀██
3 | ███ ██▐██ ██▌ ▐██ ██▌▐█████████ ▄████████▀ ██▀ ▀██
4 | ▄██▀██▄ ██▐██ ▐██ ██▌ ██▌██ ▐█▌ ▀██▄ ██▀ ▀██
5 | ▄██▀ ▀██▄ ██▐██ ▀███▀ ██▌▀█████████▐█▌ ▀██▄██▀ ▀██
6 |
7 |
8 |
9 | Based on a rendering originally drawn by:
10 | http://my.asciiart.club/
11 |
--------------------------------------------------------------------------------
/public/stylesheets/dialogue.scss:
--------------------------------------------------------------------------------
1 | dl.dialogue {
2 | margin-top: 18pt;
3 | margin-bottom: 18pt;
4 | @extend .row;
5 | }
6 |
7 | dl.dialogue dt {
8 | @extend .col-md-2;
9 | }
10 |
11 | dl.dialogue dd {
12 | @extend .col-md-10;
13 | }
14 |
15 | @media screen and (min-width: 48em){
16 | dl.dialogue dt {
17 | // @extend .text-right; but that doesn't work inside a media query
18 | text-align: right !important;
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/views/layouts/includes/helpers.pug:
--------------------------------------------------------------------------------
1 | mixin breadcrumbs(links,titles)
2 | nav(aria-label="Breadcrumb",class="breadcrumb")
3 | ul.breadcrumb
4 | li
5 | a(href="/")
6 | | Home
7 | - var i = 0;
8 | - var linkCount = links.length
9 | - while ( i < linkCount - 1 )
10 | li
11 | a(href=links[i])
12 | | #{titles[i]}
13 | - i++;
14 | li.active(aria-current="page")
15 | | #{titles[linkCount - 1]}
16 |
--------------------------------------------------------------------------------
/public_key.pem:
--------------------------------------------------------------------------------
1 | -----BEGIN PUBLIC KEY-----
2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA5PvZZosTXYPanSLDWpUl
3 | rhUJwKfcdXXq01WUb7i+6A38ub1d7RTesJhpctC7aJgNtK2YlSSzlFn0qob1Rk2Q
4 | 8dbMTndQ33nnp0UhcrOWE123qy3AaWy/cskQ/uPaHMSdsV/VWBj/qsoSsap3l4Ar
5 | mg9ZSBOmIO1+aIQWcax6xDKm1/zHKh7g2fXSaeeWObaVI7GWCp5oLWfiDkvO33jn
6 | fS9AUw4JhB7nEhZKJ1l1jDYBL8GxAvhQ4YyRoQsEzSiqJR7l/B62oqs2yk5YPYQM
7 | XiEl5QE864U9wXRja//0y+dMr3JPAcH+gehEog0qJlX4piYcJHZOlUepghQbVj6z
8 | mwIDAQAB
9 | -----END PUBLIC KEY-----
10 |
--------------------------------------------------------------------------------
/public/stylesheets/latex.scss:
--------------------------------------------------------------------------------
1 |
2 | .tex sub, .other-latex sub, .other-latex sup {
3 | text-transform: uppercase;
4 | }
5 |
6 | .tex sub, .other-latex sub {
7 | vertical-align: 0.15ex;
8 | margin-left: -0.1667em;
9 | margin-right: -0.125em;
10 | }
11 |
12 | .tex, .other-latex, .tex sub, .other-latex sub {
13 | font-size: 1em;
14 | }
15 |
16 | .other-latex sup {
17 | font-size: 0.75em;
18 | vertical-align: -0.15em;
19 | margin-left: -0.36em;
20 | margin-right: -0.15em;
21 | }
22 |
23 |
--------------------------------------------------------------------------------
/public/javascripts/image-environment.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 | window.jQuery = $;
3 |
4 | $(function() {
5 | $('div.image-environment').each( function() {
6 |
7 | var imageEnvironment = $(this);
8 |
9 | $('img', imageEnvironment).each( function() {
10 | var img = $(this);
11 | var href = img.attr('src');
12 |
13 | var link = $('');
14 | link.attr('href', href);
15 |
16 | link.append( img );
17 | imageEnvironment.append( link );
18 | });
19 | });
20 |
21 | });
22 |
23 |
--------------------------------------------------------------------------------
/routes/xourses.js:
--------------------------------------------------------------------------------
1 |
2 |
3 | exports.index = function(req, res, next) {
4 | var xourses = [];
5 |
6 | if (req.repositoryMetadata === undefined) {
7 | next(null);
8 | return;
9 | }
10 |
11 | Object.keys(req.repositoryMetadata.xourses).forEach( function(xoursePath) {
12 | var x = req.repositoryMetadata.xourses[xoursePath];
13 | x.path = xoursePath;
14 | xourses.push( x );
15 | });
16 |
17 | res.render('xourses/index', {
18 | repositoryName: req.repositoryName,
19 | xourses: xourses
20 | } );
21 | };
22 |
--------------------------------------------------------------------------------
/views/page/global-preamble.pug:
--------------------------------------------------------------------------------
1 | div(style="display: none;")
2 | script(type="math/tex")
3 | | \newcommand{\arraycolsep}[3]{}
4 | | \newcommand{\dd}[2][]{\frac{d #1}{d #2}}
5 | | \newcommand{\pp}[2][]{\frac{\partial #1}{\partial #2}}
6 | | \newcommand{\veci}{\vec{\hat{\bf\imath}}}
7 | | \newcommand{\vecj}{\vec{\hat{\bf\jmath}}}
8 | | \newcommand{\utan}{\vec{\hat{t}}}
9 | | \newcommand{\unormal}{\vec{\hat{n}}}
10 | | \newenvironment{amatrix}[1]{%
11 | | \begin{bmatrix}
12 | | }{%
13 | | \end{bmatrix}
14 | | }
15 |
--------------------------------------------------------------------------------
/public/stylesheets/abstract.scss:
--------------------------------------------------------------------------------
1 | /* The styling for the abstract */
2 | .abstract p:first-child:before {
3 | font-weight:bold;
4 | content:"Abstract.";
5 | font-style:normal;
6 | display: inline;
7 | padding-right: 0.5em;
8 | font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
9 | }
10 |
11 | .abstract {
12 | text-align:justify;
13 | font-style:italic;
14 | margin-top:2em;
15 | margin-bottom:2em;
16 | color: gray;
17 | }
18 |
19 | .abstract:empty {
20 | display: none;
21 | }
22 |
--------------------------------------------------------------------------------
/views/page/navigation.pug:
--------------------------------------------------------------------------------
1 | include ../activity-card.pug
2 |
3 | if (activity.xourse.activityList.length > 0)
4 | nav(role="navigation",aria-label="textbook")
5 | div.kinetic.course-navigation.card-group.scroll(data-commit=activity.commit, data-points=activity.xourse.totalPoints, data-xourse-url=`${repositoryName}/${activity.xourse.path}`)
6 | for activityUrl in activity.xourse.activityList
7 | +xourseCard(repositoryName, activity.xourse.path, activityUrl, activity.xourse.activities[activityUrl], activityUrl == activity.path)
8 | div.scrollbar
9 | div.handle
10 |
--------------------------------------------------------------------------------
/views/404.pug:
--------------------------------------------------------------------------------
1 | extends layouts/main
2 |
3 | block title
4 | | 404
5 |
6 | block content
7 | .container
8 | .row.d-flex.align-items-center.justify-content-around
9 | div.mt-5
10 | h1(style="font-size: 100pt;") 404
11 | h1.text-muted I am so sorry.
12 | div.mt-5
13 | h3 The page you want could not be found.
14 | p I could not find the page #{url} but I will look into the cause of this error.
15 | if repositoryName
16 | p In the meantime, perhaps look at #{repositoryName} to find what you seek.
--------------------------------------------------------------------------------
/test/four-oh-fours.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var request = require('supertest');
4 | var app = require('../app').app;
5 | var should = require('should');
6 |
7 | describe('index page', function () {
8 | it('should provide some html', function (done) {
9 | request(app)
10 | .get('/')
11 | .set('Accept', 'text/html')
12 | .expect(200, done);
13 | });
14 | });
15 |
16 | describe('missing routes', function () {
17 | it('should provide an error', function (done) {
18 | request(app)
19 | .get('/nothing-is-here')
20 | .set('Accept', 'text/html')
21 | .expect(404, done);
22 | });
23 | });
24 |
--------------------------------------------------------------------------------
/public/javascripts/rowclick.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 |
3 | // transforms any bootstrap table row that has data-href attribute set into a clickable element
4 | exports.addClickableTableRows = function() {
5 | $('.table tr[data-href]').each(function(){
6 | $(this).css('cursor','pointer').hover(
7 | function(){
8 | $(this).addClass('active');
9 | },
10 | function(){
11 | $(this).removeClass('active');
12 | }).click( function(){
13 | document.location = $(this).attr('data-href');
14 | }
15 | );
16 | });
17 | };
18 |
19 |
--------------------------------------------------------------------------------
/views/source.pug:
--------------------------------------------------------------------------------
1 | extends layouts/main
2 |
3 | block title
4 | | #{file.path}
5 |
6 | block content
7 | div.container
8 | div.pull-right.btn-toolbar(role="toolbar")
9 | //.btn-group
10 | // a.btn.btn-info(href="../")
11 | // | Do Activity
12 | //.btn-group
13 | // a.btn.btn-primary(href="https://github.com/#{activity.slug.split(':')[0]}/blob/master/#{activity.relativePath}")
14 | // | View on GitHub
15 |
16 | .page-header
17 | h1 #{file.path}
18 | small source code
19 | pre(class="brush: latex;")
20 | | #{file.data}
21 |
--------------------------------------------------------------------------------
/routes/statistics.js:
--------------------------------------------------------------------------------
1 | var fs = require("fs");
2 | var config = require('../config');
3 | var path = require('path');
4 |
5 | var lrsRoot = config.repositories.root;
6 |
7 | // BADBAD: This is horribly slow.
8 | exports.get = function(req, res, next) {
9 | console.log( req.params.repository );
10 | var filename = path.join( lrsRoot, req.params.repository + ".git", "summary.json" );
11 | var activityHash = req.params.activityHash;
12 |
13 | fs.readFile(filename, 'utf8', function (err, data) {
14 | if (err)
15 | next(err);
16 | else {
17 | var summary = JSON.parse(data);
18 | res.json( summary.activities[activityHash] );
19 | }
20 | });
21 | };
22 |
--------------------------------------------------------------------------------
/public/stylesheets/hints.scss:
--------------------------------------------------------------------------------
1 | .hint {
2 | color: gray;
3 | margin-left: 1em;
4 | }
5 |
6 | .btn-reveal-hint {
7 | float: right;
8 | clear: right;
9 | margin-bottom: 1em;
10 | margin-left: 0.5em;
11 |
12 | /* Sometimes mathjax elements cover up the hint collapse button */
13 | z-index: 1;
14 | position: relative;
15 | }
16 |
17 | .btn-hint-collapse i {
18 | transition-duration: 0.2s;
19 | transition-property: transform;
20 | }
21 |
22 | .btn-hint-collapse {
23 | float: right;
24 | clear: right;
25 | margin: 0.25em;
26 | margin-right: 0;
27 | padding: 0;
28 | /* Sometimes mathjax elements cover up the hint collapse button */
29 | z-index: 1;
30 | position: relative;
31 | }
32 |
--------------------------------------------------------------------------------
/views/xourses/index.pug:
--------------------------------------------------------------------------------
1 | extends ../layouts/main
2 |
3 | block title
4 | | #{repositoryName}
5 |
6 | block content
7 | nav(aria-label="breadcrumb",role="navigation")
8 | ol.breadcrumb
9 | li.breadcrumb-item.active
10 | | !{repositoryName}
11 |
12 | .container.ml-5.p-3.d-flex.align-items-start
13 | for xourse in xourses
14 | if xourse.title
15 | .card.m-2.xourse-card(style="width: 30%;")
16 | a(href=`/${repositoryName}/${xourse.path}`)
17 | if xourse.logo
18 | img.card-img-top(src=`${repositoryName}/${xourse.logo}`,style="width: 100%;")
19 | else
20 | img.card-img-top(style="width: 100%;")
21 | .card-block
22 | h4.card-title #{xourse.title}
23 | p.card-text !{xourse.abstract}
24 |
25 |
--------------------------------------------------------------------------------
/public/javascripts/desmos.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 |
3 | var DesmosNeeded = false;
4 |
5 | exports.promise = $.Deferred();
6 |
7 | exports.onReady = function( callback ) {
8 | $.when(exports.promise).done( callback );
9 | };
10 |
11 | exports.loadAsynchronously = function() {
12 | if (DesmosNeeded == false) {
13 | DesmosNeeded = true;
14 |
15 | $.getScript( "https://www.desmos.com/api/v0.7/calculator.js?apiKey=dcb31709b452b1cf9dc26972add0fda6",
16 | function() {
17 | function waitForDesmos(){
18 | if(typeof window.Desmos !== "undefined"){
19 | exports.promise.resolve( window.Desmos );
20 | } else {
21 | setTimeout(function(){
22 | waitForDesmos();
23 | },250);
24 | }
25 | }
26 |
27 | waitForDesmos();
28 | });
29 | }
30 | };
31 |
--------------------------------------------------------------------------------
/public/javascripts/annotator.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 | var annotator = require('annotator');
3 |
4 | var activityHash = function () {
5 | return {
6 | beforeAnnotationCreated: function (ann) {
7 | // get the actual activity hash
8 | ann.activityHash = window.location.href;
9 | }
10 | };
11 | };
12 |
13 | function addAnnotator() {
14 | var app = new annotator.App();
15 | app.include(annotator.ui.main, {element: this})
16 | .include(annotator.storage.http,
17 | {prefix: 'http://example.com/api'})
18 | .include(activityHash);
19 | app.start()
20 | .then(function () {
21 | app.annotations.load({activityHash: window.location.href});
22 | });
23 | }
24 |
25 | $.fn.extend({
26 | annotator: function() {
27 | return this.each( addAnnotator );
28 | }
29 | });
30 |
--------------------------------------------------------------------------------
/views/modals/reset.pug:
--------------------------------------------------------------------------------
1 | .modal.fade#resetWarningModal(tabindex="-1",role="dialog",aria-labelledby="updateWarningModal")
2 | .modal-dialog(role="document")
3 | .modal-content
4 | .modal-header
5 | h5.modal-title Warning
6 | button.close(type="button",data-dismiss="modal",aria-label="Close")
7 | span(aria-hidden="true") ×
8 | .modal-body
9 | p You are about to erase your work on this activity. Are you sure you want to do this?
10 | .modal-footer
11 | button.btn.btn-primary(type="button",data-dismiss="modal")
12 | | No, keep my work.
13 | button.btn.btn-danger#reset-work-button(type="button", data-dismiss="modal")
14 | | Yes, delete my work.
15 |
--------------------------------------------------------------------------------
/views/lti/passback.pug:
--------------------------------------------------------------------------------
1 | doctype xml
2 | imsx_POXEnvelopeRequest(xmlns="http://www.imsglobal.org/services/ltiv1p1/xsd/imsoms_v1p0")
3 | imsx_POXHeader
4 | imsx_POXRequestHeaderInfo
5 | imsx_version V1.0
6 | if messageIdentifier
7 | imsx_messageIdentifier #{messageIdentifier}
8 | imsx_POXBody
9 | replaceResultRequest
10 | resultRecord
11 | if sourcedId
12 | sourcedGUID
13 | sourcedId #{sourcedId}
14 | result
15 | if resultDataUrl
16 | resultData
17 | url #{resultDataUrl}
18 | if resultTotalScore
19 | resultTotalScore
20 | language en
21 | textString #{resultTotalScore}
22 | if resultScore
23 | resultScore
24 | language en
25 | textString #{resultScore}
26 |
--------------------------------------------------------------------------------
/views/modals/update-xourse.pug:
--------------------------------------------------------------------------------
1 | .modal.fade#updateWarningModal(tabindex="-1",role="dialog",aria-labelledby="updateWarningModal")
2 | .modal-dialog(role="document")
3 | .modal-content
4 | .modal-header
5 | h5(class="modal-title",id="updateModalLabel") Updated Version Available
6 | button.close(type="button",data-dismiss="modal",aria-label="Close")
7 | span(aria-hidden="true") ×
8 | .modal-body
9 | p There is an updated version of this page. How would you like to proceed?
10 | .modal-footer
11 | button.btn.btn-secondary(type="button",data-dismiss="modal")
12 | | Stay with this old version.
13 | a.btn.btn-primary#update-version-button(href="#")
14 | | Update to the new version.
15 |
--------------------------------------------------------------------------------
/views/modals/confirm-deletion.pug:
--------------------------------------------------------------------------------
1 | div.modal.fade#confirm-delete(tabindex="-1",role="dialog",aria-labelledby="confirm-delete")
2 | div.modal-dialog(role="document")
3 | div.modal-content
4 | div.modal-header
5 | h5(class="modal-title") Confirm Deletion
6 | button.close(type="button",data-dismiss="modal",aria-label="Close")
7 | span(aria-hidden="true") ×
8 | div.modal-body
9 | p Are you sure you want to disconnect your account from your Learning Management System?
10 | div.modal-footer
11 | button.btn.btn-default(type="button", data-dismiss="modal")
12 | | No, I want to stay connected
13 | button.btn.btn-danger#disconnect-button
14 | | Yes, disconnect my account.
15 |
--------------------------------------------------------------------------------
/views/layouts/navbar/save.pug:
--------------------------------------------------------------------------------
1 | if activity
2 | button.btn.btn-primary.mx-1(type="button", id="save-work-button")
3 | span(style="display: none;", id="work-error")
4 | |
5 | span.hidden-md-down Failed
6 | span(style="display: none;", id="work-saved")
7 | |
8 | span.hidden-md-down Saved!
9 | span(style="display: none;", id="work-saving")
10 | |
11 | span.hidden-md-down Saving…
12 | span(style="display: none;", id="work-reconnecting")
13 | |
14 | span.hidden-md-down Reconnecting…
15 | span(id="work-save")
16 | |
17 | span.hidden-md-down Save
18 |
--------------------------------------------------------------------------------
/views/layouts/navbar/index.pug:
--------------------------------------------------------------------------------
1 | //div#scroller-anchor
2 | nav.navbar.sticky-top.navbar-toggleable.navbar-light.bg-faded(role='navigation')
3 | button.navbar-toggler.navbar-toggler-right(type="button",data-toggle="collapse",data-target="#navbarNav",aria-controls="navbarNav",aria-expanded="false",aria-label="Toggle navigation")
4 | span.navbar-toggler-icon
5 | include brand
6 | .navbar-collapse.collapse#navbarNav
7 | ul.navbar-nav
8 | include due-date
9 | include statistics
10 | if !landingPage
11 | include get-help
12 | ul.navbar-nav.ml-auto
13 | if xourse && (!instructors)
14 | include search
15 | include another
16 | include palette
17 | include save
18 | include update
19 | include erase
20 | include edit
21 | if activity
22 | include progress-bar
23 | include login
24 |
--------------------------------------------------------------------------------
/deploy.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if [ $(hostname) = ximera-1.asc.ohio-state.edu ]; then
3 | echo On the deployment machine.
4 | echo Pulling latest version from github, protecting our dotenv...
5 | mv -f .env .env.backup
6 | git pull
7 | mv -f .env.backup .env
8 | echo Updating npm...
9 | npm install
10 | echo Running gulp...
11 | node ./node_modules/gulp/bin/gulp.js js
12 | node ./node_modules/gulp/bin/gulp.js service-worker
13 | node ./node_modules/gulp/bin/gulp.js css
14 | echo Stopping old copies of app.js...
15 | pm2 stop ximera
16 | echo Starting a new copy of app.js...
17 | pm2 start ecosystem.config.js --env production
18 | else
19 | echo not on the deployment machine...
20 | echo copying environment and keys to deployment machine...
21 | rsync -avz -f"- .git/" private_key.pem .env ximera:/var/www/apps/ximera
22 | ssh ximera "cd /var/www/apps/ximera ; source deploy.sh"
23 | fi
24 |
--------------------------------------------------------------------------------
/routes/etag.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore');
2 |
3 | function readIfNoneMatch( req ) {
4 | var header = req.get('If-None-Match');
5 |
6 | if (!header) return [];
7 | // Ignore the weakness of any tags
8 | header = header.replace( /W\/\"/, "\"" );
9 |
10 | try {
11 | return JSON.parse("[" + header + "]");
12 | } catch(e) {
13 | return [header];
14 | }
15 | }
16 |
17 | exports.checkIfNoneMatch = function( req, res, etag, callback ) {
18 | // If the requester claims to already have our hash...
19 | if (_.some( readIfNoneMatch(req), h => h == etag )) {
20 | // Then tell the browser that they're good to go.
21 | res.set({ 'ETag': '"' + etag + '"' });
22 | res.sendStatus(304);
23 | return;
24 | }
25 |
26 | // If not, call the callback to expensively recreate our content...
27 | callback( function( res ) {
28 | // And help the callback to set the etag
29 | res.set({ 'ETag': '"' + etag + '"' });
30 | });
31 | }
32 |
--------------------------------------------------------------------------------
/views/layouts/includes/osu.pug:
--------------------------------------------------------------------------------
1 | div#osu_navbar(role="navigation")
2 | h2#osu_navbar_heading.osu-semantic Ohio State nav bar
3 | a#skip.osu-semantic(href="#page-content") Skip to main content
4 | div.container
5 | div.univ_info
6 | p.univ_name
7 | a(href="http://osu.edu",title="The Ohio State University") The Ohio State University
8 | div.univ_links
9 | div.links
10 | ul
11 | li
12 | a(href="http://www.osu.edu/help.php",class="help") Help
13 | li
14 | a(href="http://buckeyelink.osu.edu/",class="buckeyelink") BuckeyeLink
15 | li
16 | a(href="http://www.osu.edu/map/",class="map") Map
17 | li
18 | a(href="http://www.osu.edu/findpeople.php",class="findpeople") Find People
19 | li
20 | a(href="https://email.osu.edu/",class="webmail") Webmail
21 | li
22 | a(href="http://www.osu.edu/search/",class="search") Search Ohio State
23 |
24 |
25 |
--------------------------------------------------------------------------------
/public/stylesheets/pulsate.scss:
--------------------------------------------------------------------------------
1 | $pulsate-list: btn-primary, btn-success, btn-info, btn-warning, btn-danger, btn-inverse, page-link;
2 |
3 | $pulsate-colors: (
4 | btn-primary: $brand-primary,
5 | btn-success: $brand-success,
6 | btn-info: $brand-info,
7 | btn-warning: $brand-warning,
8 | btn-danger: $brand-danger,
9 | btn-inverse: $brand-inverse,
10 | page-link: $brand-primary
11 | );
12 |
13 | @each $pulsator in $pulsate-list {
14 |
15 | .disabled .#{$pulsator}.pulsate {
16 | animation: none;
17 | text-shadow: none;
18 | }
19 |
20 | .#{$pulsator}.pulsate {
21 | animation: pulsate-#{$pulsator} 0.5s infinite alternate;
22 | animation-timing-function: linear;
23 | text-shadow: 0 0 8px #ccc;
24 | }
25 |
26 | @keyframes pulsate-#{$pulsator} {
27 | from { box-shadow: 0 0 10px #333; transform: rotate(-2deg); }
28 | to { box-shadow: 0 0 20px map-get($pulsate-colors,$pulsator); transform: rotate(2deg); }
29 | }
30 | }
31 |
32 |
--------------------------------------------------------------------------------
/public/stylesheets/flash.scss:
--------------------------------------------------------------------------------
1 | .flash {
2 | -moz-animation: flash 5s ease-out;
3 | -moz-animation-iteration-count: 1;
4 |
5 | -webkit-animation: flash 5s ease-out;
6 | -webkit-animation-iteration-count: 1;
7 |
8 | -ms-animation: flash 5s ease-out;
9 | -ms-animation-iteration-count: 1;
10 | }
11 |
12 | @keyframes flash {
13 | 0% { background-color: transparent; }
14 | 50% { background-color: #fbf8b2; }
15 | 100% { background-color: transparent; }
16 | }
17 |
18 | @-webkit-keyframes flash {
19 | 0% { background-color: transparent; }
20 | 50% { background-color: #fbf8b2; }
21 | 100% { background-color: transparent; }
22 | }
23 |
24 | @-moz-keyframes flash {
25 | 0% { background-color: transparent; }
26 | 50% { background-color: #fbf8b2; }
27 | 100% { background-color: transparent; }
28 | }
29 |
30 | @-ms-keyframes flash {
31 | 0% { background-color: transparent; }
32 | 50% { background-color: #fbf8b2; }
33 | 100% { background-color: transparent; }
34 | }
35 |
--------------------------------------------------------------------------------
/views/install.sh:
--------------------------------------------------------------------------------
1 | #! /bin/bash
2 |
3 | echo "Create a texmf directory..."
4 | mkdir -p ~/texmf/tex/latex
5 |
6 | echo "Clone or update ximeraLatex..."
7 | (cd ~/texmf/tex/latex && git clone https://github.com/XimeraProject/ximeraLatex.git) || (cd ~/texmf/tex/latex/ximeraLatex && git pull)
8 |
9 | echo "Download mutool from a repository..."
10 | mkdir -p ~/.local/bin
11 | wget -qO- https://www.archlinux.org/packages/community/x86_64/mupdf-tools/download/ | tar -xJf - usr/bin/mutool -O > ~/.local/bin/mutool
12 | chmod +x ~/.local/bin/mutool
13 |
14 | echo "Clone or update xake..."
15 | (cd ~ && git clone https://github.com/XimeraProject/xake.git) || (cd ~/xake && git pull)
16 |
17 | echo "Install dependencies for xake..."
18 | cd ~/xake && npm install
19 |
20 | echo "Add the 'xake' command..."
21 | mkdir -p ~/.local/bin
22 | ln -s ~/xake/app.js ~/.local/bin/xake
23 |
24 | echo "Download some demo content..."
25 | (cd ~ && git clone https://github.com/xandbox/xandbox.git) || (cd ~/xandbox && git pull)
26 |
27 | echo "Done."
28 |
--------------------------------------------------------------------------------
/views/certificate/xourse.pug:
--------------------------------------------------------------------------------
1 | extends ../layouts/main
2 |
3 | block title
4 | | #{xourse.title}
5 |
6 | block modals
7 |
8 | block content
9 | div.container
10 | .page-header
11 | h1 #{xourse.title}
12 |
13 | div
14 | - var percent = function(p) { return (p * 100).toFixed(2).toString() + '%'; };
15 | dl.certificate
16 | for activityUrl in xourse.activityList
17 | - var activity = xourse.activities[activityUrl];
18 | if activityUrl.match(/^#/)
19 | dt.part !{activity.title}
20 | dd
21 | else
22 | dt.page
23 | a(href=activityUrl) !{activity.title}
24 | dd #{percent(activity.completion)}
25 | hr
26 | dt.total Overall
27 | dd #{score.toString() + '%'}
28 |
29 | p
30 | a(href=`/certificate/${escapedCode}/${escapedSignature}`)
31 | span.certificate https://ximera.osu.edu/certificate/#{escapedCode}/#{escapedSignature}
32 |
--------------------------------------------------------------------------------
/views/modals/update.pug:
--------------------------------------------------------------------------------
1 | .modal.fade#updateWarningModal(tabindex="-1",role="dialog",aria-labelledby="updateWarningModal")
2 | .modal-dialog(role="document")
3 | .modal-content
4 | .modal-header
5 | h5(class="modal-title",id="updateModalLabel") Updated Version Available
6 | button.close(type="button",data-dismiss="modal",aria-label="Close")
7 | span(aria-hidden="true") ×
8 | .modal-body
9 | p There is an updated version of this activity. If you update to the most recent version of this activity, then your current progress on this activity will be erased. Regardless, your record of completion will remain. How would you like to proceed?
10 | .modal-footer
11 | button.btn.btn-default(type="button",data-dismiss="modal")
12 | | Keep the old version.
13 | a.btn.btn-warning#update-version-button(href="#")
14 | | Delete my work and update to the new version.
15 |
--------------------------------------------------------------------------------
/views/page/next-and-previous.pug:
--------------------------------------------------------------------------------
1 | if previousActivity || nextActivity
2 | nav.my-4(aria-label="page navigation")
3 | ul.pagination.justify-content-start
4 | li.page-item#previous-activity(class=(previousActivity ? undefined : "disabled"))
5 | if previousActivity
6 | a.page-link(href="/" + repositoryName + "/" + activity.xourse.path + "/" + previousActivity,aria-label="previous activity")
7 | | ← Previous
8 | else
9 | span.page-link(aria-label="previous activity",href="#")
10 | | ← Previous
11 | li.page-item.ml-auto#next-activity(class=(nextActivity ? undefined : "disabled"),completion-blink="",aria-label="next activity")
12 | if nextActivity
13 | a.page-link(href="/" + repositoryName + "/" + activity.xourse.path + "/" + nextActivity)
14 | | Next →
15 | else
16 | span.page-link(aria-label="next activity")
17 | | Next →
18 |
--------------------------------------------------------------------------------
/blog/shuttleworth.markdown:
--------------------------------------------------------------------------------
1 | {{{
2 | "title" : "Snapp awarded Shuttleworth Flash Grant",
3 | "tags" : [ "shuttleworth foundation" ],
4 | "category" : "ximera funding",
5 | "date" : "6-01-2014",
6 | "author" : "Bart Snapp"
7 | }}}
8 |
9 |
10 | I'm pleased to announce that I've received a Shuttleworth Flash Grant!
11 | This grant is given to help burgeoning ideas come to life. We are
12 | excited to use this to help support the Ximera project.
13 |
14 |
15 |
16 | Our current thought is that these funds will be best used to help
17 | build a community of developers and authors who work with Ximera.
18 |
19 | While content in mathematics might seem like a slow-moving target, the
20 | fact is that quality content is produced through iterative
21 | development. When others can develop content and contribute to content
22 | that is already deployed, we (as a community) are able to produce
23 | superior materials that will have relevance despite the ephemeral
24 | nature of the internet.
25 |
26 | Our first workshop is July 28th to the 30th. I'm looking forward to
27 | it!
28 |
29 |
30 |
--------------------------------------------------------------------------------
/public/javascripts/brushes/shBrushLatex.js:
--------------------------------------------------------------------------------
1 | /*
2 | * Improved version of the very simple LaTeX brush from
3 | * http://www.jorgemarsal.com/blog/
4 | * some code from Gheorghe Milas and Ahmad Sherif
5 | */
6 |
7 | var BrushBase = require('brush-base');
8 | var regexLib = require('syntaxhighlighter-regex').commonRegExp;
9 |
10 | function Brush() {
11 | var keywords = 'break continue case return in eq ne gt lt ge le';
12 |
13 | this.regexList = [
14 | // one line comments
15 | { regex: new RegExp('%.*','gm'),
16 | css: 'comments' },
17 | // double quoted strings
18 | { regex: regexLib.doubleQuotedString,
19 | css: 'string' },
20 | // commands
21 | { regex: new RegExp('\\\\\\w*','gm'),
22 | css: 'keyword' },
23 | // commands
24 | { regex: new RegExp('\\$[^\\$]+\\$','gm'),
25 | css: 'color2' },
26 | // keywords
27 | { regex: new RegExp(this.getKeywords(keywords), 'gm'),
28 | css: 'function' },
29 | ];
30 |
31 | this.forHtmlScript(regexLib.scriptScriptTags);
32 | }
33 |
34 | Brush.prototype = new BrushBase();
35 | Brush.aliases = ['tex', 'latex', 'LaTeX', 'TeX'];
36 | module.exports = Brush;
37 |
--------------------------------------------------------------------------------
/public/stylesheets/chat.scss:
--------------------------------------------------------------------------------
1 | .chat {
2 | position: fixed;
3 | bottom: 0;
4 | right: 0;
5 | width: 30vw;
6 | background-color: white;
7 | z-index: 9000;
8 | border-top: black 1px solid;
9 | border-left: black 1px solid;
10 | }
11 |
12 | .chat h5 {
13 | margin: 1ex;
14 | }
15 |
16 | .chat .transcript {
17 | border: none;
18 | border-top: #eee 1px solid;
19 | border-bottom: #eee 1px solid;
20 | max-height: 144pt;
21 | padding: 1ex;
22 | overflow: hidden;
23 | overflow-y: scroll;
24 | margin: 0;
25 | }
26 |
27 | .chat input {
28 | width: 100%;
29 | border: none;
30 | padding: 2pt;
31 | }
32 |
33 |
34 | .chat dl {
35 | }
36 |
37 | .chat dl dt {
38 | float: left;
39 | margin-right: 1em;
40 | color: #aaa;
41 | }
42 |
43 | .chat dl dd {
44 | border-top: #eee 1px solid;
45 | display: flex;
46 | }
47 |
48 |
49 | .chat dl dt.other {
50 | float: right;
51 | margin: 0;
52 | margin-left: 1em;
53 | text-align: right;
54 | clear: right;
55 | }
56 |
57 | .chat dl dd.other {
58 | background: white;
59 | text-align: right;
60 | }
61 |
--------------------------------------------------------------------------------
/public/javascripts/version.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 |
3 | // Check to see if there is a newer version available
4 | var version = require('../../package.json').version;
5 | console.log("This is XIMERA, Version " + version );
6 |
7 | $(function() {
8 | // Check which version the server is providing, avoiding the cache
9 | $.ajax( "/version?" + (new Date().getTime()) )
10 | .done(function(data) {
11 | // If the server can offer a newer version, let's update
12 | if (data != version) {
13 | if (sessionStorage.getItem('refreshedToVersion') == data) {
14 | alert('Attempted to refresh; try a force refresh.');
15 | } else {
16 | sessionStorage.setItem('refreshedToVersion', data);
17 |
18 | console.log("Uppdating from version " + version + " to version " + data );
19 |
20 | // This is MAYBE needed in Chrome
21 | $.ajax({
22 | url: window.location.href,
23 | headers: {
24 | "Pragma": "no-cache",
25 | "Expires": -1,
26 | "Cache-Control": "no-cache"
27 | }
28 | }).done(function () {
29 | window.location.reload(true);
30 | });
31 | }
32 | }
33 | });
34 | });
35 |
--------------------------------------------------------------------------------
/public/javascripts/instructor.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 |
3 | function announce( hash, answers ) {
4 | var selector = function(hash, problem, answerable) {
5 | return "[data-hash='" + hash + "'] " + "#" + problem + " #" + answerable;
6 | };
7 |
8 | Object.keys(answers).forEach( function(problem) {
9 | Object.keys(answers[problem]).forEach( function(answerable) {
10 | var element = $(selector(hash, problem, answerable));
11 | var statistics = answers[problem][answerable];
12 |
13 | element.trigger( "ximera:statistics:answers", statistics.responses );
14 | element.trigger( "ximera:statistics:successes", statistics.successes );
15 | });
16 | });
17 | }
18 |
19 | $(function() {
20 | $("#instructor-view-statistics").click( function() {
21 | $("#instructor-view-statistics").hide();
22 |
23 | var url = $(this).attr('data-activity-url');
24 | var hash = $(this).attr('data-activity-hash');
25 |
26 | $.ajax({
27 | url: '/statistics/' + url + '/' + hash,
28 | type: 'GET',
29 | success: function(result) {
30 | if (result)
31 | announce( hash, result );
32 | }
33 | });
34 | });
35 | });
36 |
37 |
38 |
--------------------------------------------------------------------------------
/blog/xw1Day3.markdown:
--------------------------------------------------------------------------------
1 | {{{
2 | "title" : "Ximera Workshop 1: Day 3",
3 | "tags" : [ "shuttleworth foundation, nsf, workshop" ],
4 | "category" : "ximera workshop",
5 | "date" : "7-30-2014",
6 | "author" : "Bart Snapp"
7 | }}}
8 |
9 | Today was the final day of the Ximera Workshop 1. While three days is
10 | not much time, I am very impressed with how much we have
11 | achieved. Thank you so much to everyone involved, and thanks again to
12 | the NSF and the Shuttleworth Foundation for making this possible.
13 |
14 |
15 |
16 | With impassioned discussion, we laid the ground work for the future
17 | of Ximera. This is not to say that we didn't achieve much now, quite
18 | the contrary: Randy Pirum prototype a Ximera Data Viewer, Chris
19 | Cunningham showed an Ximera activity that could identify units, and
20 | give constructive feedback to the students, and Chris Hill explored
21 | the creation of Ximera involving English language questions.
22 |
23 | I cannot express how excited I am to see how others use this
24 | platform. It is my hope that we can provide tools that allow content
25 | authors to unleash their own creativity, and build something that has
26 | never before been seen.
27 |
28 |
--------------------------------------------------------------------------------
/public/images/icons/favicon/Makefile:
--------------------------------------------------------------------------------
1 | all: favicon-114x114.png favicon-72x72.png favicon-54x54.png favicon.ico
2 |
3 | clean:
4 | rm -f favicon-114x114.png favicon-72x72.png favicon-54x54.png favicon.ico
5 |
6 | favicon-114x114.png: icon.png
7 | convert -geometry 114x114 icon.png favicon-114x114.png
8 |
9 | favicon-72x72.png: icon.png
10 | convert -geometry 72x72 icon.png favicon-72x72.png
11 |
12 | favicon-54x54.png: icon.png
13 | convert -geometry 54x54 icon.png favicon-54x54.png
14 |
15 | favicon-64x64.png: icon.png
16 | convert -geometry 64x64 icon.png favicon-64x64.png
17 |
18 | favicon.ico: favicon-64x64.png
19 | convert favicon-64x64.png -bordercolor white -border 0 \
20 | \( -clone 0 -resize 16x16 \) \
21 | \( -clone 0 -resize 32x32 \) \
22 | \( -clone 0 -resize 48x48 \) \
23 | \( -clone 0 -resize 64x64 \) \
24 | -delete 0 -alpha off -colors 256 favicon.ico
25 |
26 | icon.png: icon.xcf
27 | echo '(let* ((image (car (gimp-file-load RUN-NONINTERACTIVE "icon.xcf" "icon.xcf"))) (layer (car (gimp-image-merge-visible-layers image CLIP-TO-IMAGE))) (filename "icon.png")) (gimp-file-save RUN-NONINTERACTIVE image layer filename filename) (gimp-image-delete image) (gimp-quit 0))' | gimp -n -i -b -
28 |
--------------------------------------------------------------------------------
/routes/cachify.js:
--------------------------------------------------------------------------------
1 | var redis = require('redis');
2 |
3 | // create a new redis client and connect to our local redis instance
4 | var client = redis.createClient({return_buffers: true});
5 |
6 | // if an error occurs, print it to the console
7 | client.on('error', function (err) {
8 | console.log("Error " + err);
9 | });
10 |
11 | exports.json = function( key, f, callback ) {
12 | client.get(key, function(err, result) {
13 | if (err) {
14 | callback(err);
15 | } else {
16 | if (result) {
17 | callback( null, JSON.parse(result) );
18 | } else {
19 | f( function(err, result) {
20 | if (err) {
21 | callback( err );
22 | } else {
23 | client.setex( key, 31557600, JSON.stringify(result) );
24 | callback( null, result );
25 | }
26 | });
27 | }
28 | }
29 | });
30 | };
31 |
32 | exports.string = function( key, f, callback ) {
33 | client.get(key, function(err, result) {
34 | if (err) {
35 | callback(err);
36 | } else {
37 | if (result) {
38 | callback( null, result );
39 | } else {
40 | f( function(err, result) {
41 | if (err) {
42 | callback( err );
43 | } else {
44 | client.setex( key, 31557600, result );
45 | callback( null, result );
46 | }
47 | });
48 | }
49 | }
50 | });
51 | };
52 |
--------------------------------------------------------------------------------
/routes/hashcash.js:
--------------------------------------------------------------------------------
1 | var url = require('url');
2 | var crypto = require('crypto');
3 |
4 | var REQUIRED_COST = 20;
5 |
6 | function verify( resource, hashcash ) {
7 | if (resource != hashcash.split(":")[3])
8 | return 0;
9 |
10 | var shasum = crypto.createHash('sha1');
11 | shasum.update(hashcash);
12 | var buffer = new Buffer(shasum.digest('hex'),"hex");
13 |
14 | var bits = 0;
15 | for (const b of buffer) {
16 | if (b == 0) bits += 8;
17 | if (b != 0) {
18 | if (b & 0x80) return bits;
19 | if (b & 0x40) return bits + 1;
20 | if (b & 0x20) return bits + 2;
21 | if (b & 0x10) return bits + 3;
22 | if (b & 0x08) return bits + 4;
23 | if (b & 0x04) return bits + 5;
24 | if (b & 0x02) return bits + 6;
25 | if (b & 0x01) return bits + 7;
26 | }
27 | }
28 |
29 | return bits;
30 | }
31 |
32 | exports.hashcash = function(req,res,next) {
33 | if (req.get('X-Hashcash')) {
34 | var hashcash = req.get('X-Hashcash');
35 | console.log( "hashcash = " + hashcash );
36 | var pathname = url.parse(req.url).pathname.substr(1);
37 | if (verify( pathname, hashcash ) < REQUIRED_COST)
38 | res.status(400).send("Not enough hashcash attached.");
39 | else
40 | next();
41 | } else {
42 | res.status(400).send("No hashcash attached.");
43 | return;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/views/page/breadcrumbs.pug:
--------------------------------------------------------------------------------
1 | nav(aria-label="breadcrumb",role="navigation")
2 | ol.breadcrumb
3 | - var level = 0;
4 | li.breadcrumb-item
5 | - level = level + 1;
6 | a(href="/" + repositoryName, aria-level=level)
7 | | !{repositoryName}
8 | if activity.xourse
9 | li.breadcrumb-item
10 | - level = level + 1;
11 | a(href="/" + repositoryName + "/" + activity.xourse.path,aria-level=level)
12 | if (activity.xourse.title) && (activity.xourse.title != '')
13 | | !{activity.xourse.title}
14 | else
15 | | Home
16 | if activity.chapter
17 | - level = level + 1;
18 | li.breadcrumb-item
19 | a(href="/" + repositoryName + "/" + activity.xourse.path + "/" + activity.chapter.href,aria-level=level) !{activity.chapter.title}
20 | - level = level + 1;
21 | li.breadcrumb-item.active(aria-current="page",aria-level=level)
22 | if activity.title
23 | | !{activity.title}
24 | else
25 | | Activity
26 |
27 | if activity
28 | if activity.author
29 | div.float-right.byline#byline #{activity.author}
30 | else
31 | if activity.xourse.author
32 | div.float-right.byline#byline #{activity.xourse.author}
33 |
--------------------------------------------------------------------------------
/blog/xw1Day2.markdown:
--------------------------------------------------------------------------------
1 | {{{
2 | "title" : "Ximera Workshop 1: Day 2",
3 | "tags" : [ "shuttleworth foundation, nsf, workshop" ],
4 | "category" : "ximera workshop",
5 | "date" : "7-29-2014",
6 | "author" : "Bart Snapp"
7 | }}}
8 |
9 | Today was the second day of the Ximera Workshop 1. On the first day,
10 | we spent much time explaining what is going on, and learning that we
11 | need to make some changes. Today we started making these changes!
12 |
13 |
14 |
15 | The nature of Ximera syntax was on the line. Honestly, I was a bit
16 | skeptical at first, but the participants were able to convince me that
17 | our past syntax is inferior to the syntax we are now working to
18 | provide. We improved deployment, and started writing many
19 | activities. To see these demo courses, see
20 | [http://ximera.osu.edu/course/](http://example.net/).
21 |
22 |
23 | I am very encouraged at the speed of creation of these basic
24 | courses. At this point, we've made it a priority to get documentation
25 | on how someone gets started with Ximera, something that is difficult
26 | since in this early stage of development we are aiming for a moving
27 | target. Nevertheless, I am encouraged by the energy of the workshop,
28 | and hope to see some great things! Again, this would not have been
29 | possible without the support of the NSF and the Shuttleworth
30 | Foundation.
31 |
32 |
--------------------------------------------------------------------------------
/public/javascripts/mathjax.js:
--------------------------------------------------------------------------------
1 | window.MathJax = {
2 | delayStartupUntil : "configured",
3 |
4 | jax: ["input/TeX","output/HTML-CSS"],
5 | extensions: ["tex2jax.js","MathMenu.js","MathZoom.js", "toMathML.js", "AssistiveMML.js", "[a11y]/accessibility-menu.js"],
6 |
7 | tex2jax: {preview: "none"},
8 |
9 | "HTML-CSS": {
10 | availableFonts: ["TeX"],
11 | imageFont: null
12 | },
13 |
14 | processEnvironments: true,
15 | showProcessingMessages: false,
16 | messageStyle: 'none',
17 |
18 | MathMenu: {
19 | showRenderer: false,
20 | showMathPlayer: false
21 | },
22 |
23 | // BADBAD: this also breaks the layout triggers
24 | // showMathMenu: false,
25 |
26 | CommonHTML: {
27 | EqnChunk: 10000,
28 | EqnChunkFactor: 1,
29 | EqnChunkDelay: 0
30 | },
31 |
32 | "fast-preview": {disabled: true},
33 |
34 | TeX: {
35 | equationNumbers: { autoNumber: "AMS" },
36 | extensions: ["AMSmath.js","AMSsymbols.js","noErrors.js","noUndefined.js","color.js","cancel.js"],
37 | noErrors: {disabled: true},
38 | Macros: {
39 | xspace: '',
40 | ensuremath: ''
41 | }
42 | },
43 |
44 | root: "/node_modules/mathjax/"
45 | };
46 |
47 | if (window.standalone)
48 | window.MathJax.root = "https://ximera.osu.edu/node_modules/mathjax";
49 |
50 | require('mathjax2');
51 |
52 | module.exports = window.MathJax;
53 |
--------------------------------------------------------------------------------
/views/page.pug:
--------------------------------------------------------------------------------
1 | extends layouts/main
2 |
3 | block title
4 | if activity.title
5 | | !{activity.title}
6 | else
7 | | Activity
8 |
9 | block modals
10 | include modals/reset
11 | include modals/update
12 | include modals/guppymath
13 |
14 | block content
15 | // BADBAD: shouldn't this be removed now that htlatex is working
16 | // fine? No! Apparently htlatex still doesn't support two optional
17 | // arguments like this.
18 | include page/global-preamble
19 | include page/navigation
20 | if activity.xourse.title
21 | include page/breadcrumbs
22 | div.container-fluid
23 | // BADBAD: only display navigation if the item can be found IN a navigation list
24 | .row
25 | .col-md-12
26 | .row.justify-content-center
27 | .col-md-10
28 | main.activity(id="theActivity", data-hash=activity.hash, data-repository-name=repositoryName, data-commit=activity.commit, data-path=activity.path, data-title=activity.title, role="main", data-learner=learner ? learner._id : undefined, data-xourse-path=activity.xourse ? activity.xourse.path : undefined, data-xourse-url=activity.xourse ? (repositoryName + '/' + activity.xourse.path) : undefined, data-points=activity.xourse ? activity.xourse.totalPoints : undefined)
29 | .activity-body#page-content
30 | | !{activity.html}
31 | include page/next-and-previous
32 |
--------------------------------------------------------------------------------
/views/user/index.pug:
--------------------------------------------------------------------------------
1 | extends ../layouts/main
2 |
3 | block title
4 | | Users
5 |
6 |
7 | block content
8 | .container
9 | div.row
10 | table.table
11 | thead
12 | tr
13 | th Id
14 | th Name
15 | th Seen
16 | tbody
17 | for user in users
18 | tr(data-href=`/users/${user.id}`)
19 | th(scope="row")
20 | a(href=`/users/${user.id}`) …#{user.id.slice(-5)}
21 | td
22 | if user.imageUrl
23 | img.img-responsive.img-rounded(src=user.imageUrl)
24 | else
25 | img.img-responsive.img-rounded(src=`https://secure.gravatar.com/avatar/${user.gravatar}.jpg?d=retro&s=80`)
26 | td #{user.displayName} (#{user.name})
27 | if user.lastSeen
28 | td #{moment(user.lastSeen).fromNow()}
29 | else
30 | td Never
31 | div.row
32 | div.text-center
33 | ul.pagination.pagination-lg
34 | if page > 1
35 | li
36 | a(href=page) «
37 | else
38 | li.disabled
39 | span «
40 | if page < pageCount
41 | li
42 | a(href=page+1) »
43 | else
44 | li.disabled
45 | span »
46 |
--------------------------------------------------------------------------------
/views/lti/config.pug:
--------------------------------------------------------------------------------
1 | doctype xml
2 | cartridge_basiclti_link(xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0", xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0", xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0", xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0", xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance", xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd")
3 | blti:title #{title}
4 | blti:description #{description}
5 | blti:icon
6 | blti:launch_url #{launchUrl}
7 | blti:custom
8 | lticm:property(name="unlock_at") $Canvas.assignment.unlockAt.iso8601
9 | lticm:property(name="lock_at") $Canvas.assignment.lockAt.iso8601
10 | lticm:property(name="due_at") $Canvas.assignment.dueAt.iso8601
11 | blti:extensions(platform="canvas.instructure.com")
12 | lticm:property(name="tool_id") #{launchUrl}
13 | lticm:property(name="privacy_level") public
14 | lticm:property(name="domain") #{domain}
15 | cartridge_bundle(identifierref="BLTI001_Bundle")
16 | cartridge_icon(identifierref="BLTI001_Icon")
17 |
--------------------------------------------------------------------------------
/views/xourses/view.pug:
--------------------------------------------------------------------------------
1 | extends ../layouts/main
2 | include ../activity-card.pug
3 |
4 | block title
5 | | !{xourse.title}
6 |
7 | block modals
8 | include ../modals/update-xourse
9 |
10 | block content
11 | nav(aria-label="breadcrumb",role="navigation")
12 | ol.breadcrumb
13 | li.breadcrumb-item
14 | - var level = 1;
15 | a(href="/" + repositoryName, aria-level=level)
16 | | !{repositoryName}
17 | li.breadcrumb-item.active
18 | if (xourse.title) && (xourse.title != '')
19 | | !{xourse.title}
20 | else
21 | | Home
22 |
23 | div.container.xourse-cards
24 | main.xourse.card-group(data-commit=xourse.commit, data-hash=xourse.hash, data-points=xourse.totalPoints, data-path=xourse.path, data-repository-name=repositoryName, data-learner=learner ? learner._id : undefined, data-xourse-path=xourse.path, data-xourse-url=repositoryName + '/' + xourse.path)
25 | for activityUrl in xourse.activityList
26 | +xourseCard(repositoryName, xourse.path, activityUrl, xourse.activities[activityUrl])
27 | div.container
28 | hr
29 | .row
30 | .col-md-12
31 | p You can download a
32 | a.btn.btn.btn-primary(type="button", href="/" + repositoryName + "/" + xourse.path + "/certificate")
33 | | Certificate
34 | | as a record of your successes.
35 |
36 |
--------------------------------------------------------------------------------
/branding.js:
--------------------------------------------------------------------------------
1 | var ip = require('ip');
2 | var _ = require('underscore');
3 |
4 | var brandings = {
5 | "localhost": [ip.cidrSubnet('127.0.0.0/8')],
6 | "The Ohio State University": [ip.cidrSubnet('164.107.0.0/16'),
7 | ip.cidrSubnet('140.254.0.0/16'),
8 | ip.cidrSubnet('128.146.0.0/16'),
9 | ip.cidrSubnet('192.68.143.0/24'),
10 | ip.cidrSubnet('192.12.205.0/24')],
11 | "Colorado State University": [ip.cidrSubnet('129.82.0.0/16')]
12 | };
13 |
14 |
15 | exports.middleware = function(req, res, next) {
16 | var remoteAddress = req.headers['x-forwarded-for'] ||
17 | req.connection.remoteAddress ||
18 | req.socket.remoteAddress ||
19 | req.connection.socket.remoteAddress;
20 | try {
21 | res.locals.places = [];
22 |
23 | Object.keys(brandings).forEach( function(place) {
24 | if (_.some( brandings[place], function(subnet) { return subnet.contains(remoteAddress); } )) {
25 | res.locals.places.push( place );
26 | }
27 | });
28 |
29 | if (res.locals.places.indexOf( "The Ohio State University" ) >= 0)
30 | res.locals.atOhioState = true;
31 | else
32 | res.locals.atOhioState = false;
33 |
34 | if (res.locals.places.indexOf( "Colorado State University" ) >= 0)
35 | res.locals.atColoradoState = true;
36 | else
37 | res.locals.atColoradoState = false;
38 | }
39 | catch (e) {
40 | res.locals.places = [];
41 | res.locals.atOhioState = false;
42 | }
43 |
44 | next();
45 | };
46 |
--------------------------------------------------------------------------------
/views/certificate/view.pug:
--------------------------------------------------------------------------------
1 | extends ../layouts/main
2 |
3 | block title
4 | | Valid Certificate
5 |
6 | block modals
7 |
8 | block content
9 | div.container
10 | .page-header
11 | h1 Valid Certificate
12 |
13 | .row
14 | .col-md-7
15 | p
16 | | As of
17 | time(class="relative-date", datetime=certificate.date)
18 | | #{certificate.date}
19 | | on
20 | time(class="absolute-date", datetime=certificate.date)
21 | | #{certificate.date}
22 | | ,
23 | a(href=('https://' + certificate.user))
24 | | #{certificate.name}
25 | if certificate.email
26 | | <#{certificate.email}>
27 | | has earned #{certificate.score}/100 in
28 | if certificate.title === undefined
29 | a(href=certificate.course)
30 | | #{certificate.course}
31 | else
32 | a(href=certificate.course)
33 | | #{certificate.title}
34 | | .
35 | p Certificates can be verified with this public key.
36 | pre #{publicKey.trim()}
37 | .col-md-5
38 | canvas.qrcode.float-right(style="max-width: 100%;", data-href=`https://ximera.osu.edu/certificate/${escapedCode}/${escapedSignature}`)
39 | p
40 | a(href=`/certificate/${escapedCode}/${escapedSignature}`)
41 | span.certificate https://ximera.osu.edu/certificate/#{escapedCode}/#{escapedSignature}
42 |
--------------------------------------------------------------------------------
/public/javascripts/feedback.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 | var _ = require('underscore');
3 | var database = require('./database');
4 |
5 | var createFeedback = function() {
6 | var feedback = $(this);
7 |
8 | feedback.persistentData( function() {
9 | if (feedback.persistentData( 'available' )) {
10 | feedback.css({visibility: 'visible', position:'relative'});
11 | feedback.fadeTo('slow', 1);
12 | } else {
13 | feedback.css({visibility: 'hidden', position:'absolute'});
14 | feedback.css({opacity: 0});
15 | }
16 | });
17 |
18 | var problem = feedback.parents( ".problem-environment" ).first();
19 |
20 | if (feedback.attr('data-feedback') == 'attempt') {
21 | problem.on( 'ximera:attempt', function(event) {
22 | feedback.persistentData( 'available', true );
23 | });
24 | }
25 |
26 | if (feedback.attr('data-feedback') == 'correct') {
27 | problem.on( 'ximera:correct', function(event) {
28 | feedback.persistentData( 'available', true );
29 | });
30 | }
31 |
32 | if (feedback.attr('data-feedback') == 'script') {
33 | problem.on( 'ximera:attempt', function(event) {
34 | var release = false;
35 | try {
36 | release = window[feedback.attr('id')]();
37 | } catch(err) {
38 | release = false;
39 | }
40 |
41 | feedback.persistentData( 'available', release );
42 | });
43 |
44 | console.log( feedback );
45 | }
46 |
47 | };
48 |
49 | $.fn.extend({
50 | feedback: function() {
51 | return this.each( createFeedback );
52 | }
53 | });
54 |
55 |
--------------------------------------------------------------------------------
/public/javascripts/hint.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 | var _ = require('underscore');
3 | var database = require('./database');
4 |
5 | var buttonHtml = '';
6 |
7 | var createHint = function() {
8 | var hint = $(this);
9 |
10 | hint.addClass('collapse');
11 | hint.collapse('hide');
12 |
13 | var button = $(buttonHtml);
14 | hint.before(button);
15 |
16 | button.click( function() {
17 | if (hint.persistentData( 'collapsed' )) {
18 | hint.persistentData( 'collapsed', false );
19 | } else {
20 | hint.persistentData( 'collapsed', true );
21 | }
22 | });
23 |
24 | hint.trigger( 'ximera:register-hint' );
25 |
26 | hint.persistentData( function(event) {
27 | if (hint.persistentData( 'available' )) {
28 | button.show('fast');
29 |
30 | if (hint.persistentData( 'collapsed' )) {
31 | button.find('i').addClass('fa-rotate-90');
32 | hint.collapse('hide');
33 | } else {
34 | button.find('i').removeClass('fa-rotate-90');
35 | hint.collapse('show');
36 | }
37 | } else {
38 | hint.collapse('hide');
39 | button.hide();
40 | }
41 | /*
42 | if (hint.persistentData( 'available' )) {
43 |
44 | } else {
45 | button.hide();
46 | }
47 | */
48 |
49 | });
50 |
51 |
52 | };
53 |
54 | $.fn.extend({
55 | hint: function() {
56 | return this.each( createHint );
57 | }
58 | });
59 |
60 |
61 |
--------------------------------------------------------------------------------
/public/stylesheets/mathjax.scss:
--------------------------------------------------------------------------------
1 | ////////////////////////////////////////////////////////////////
2 | // align font with mathjax
3 |
4 | @font-face {
5 | font-family: 'MJX_Math';
6 | src: url('/node_modules/mathjax/fonts/HTML-CSS/TeX/eot/MathJax_Math-Italic.eot'); /* IE9 Compat Modes */
7 | src: url('/node_modules/mathjax/fonts/HTML-CSS/TeX/eot/MathJax_Math-Italic.eot?iefix') format('eot'),
8 | url('/node_modules/mathjax/fonts/HTML-CSS/TeX/woff/MathJax_Math-Italic.woff') format('woff'),
9 | url('/node_modules/mathjax/fonts/HTML-CSS/TeX/otf/MathJax_Math-Italic.otf') format('opentype'),
10 | url('/node_modules/mathjax/fonts/HTML-CSS/TeX/svg/MathJax_Math-Italic.svg#MathJax_Math-Italic') format('svg');
11 | }
12 |
13 | @font-face {
14 | font-family: 'MJX_Main';
15 | src: url('/node_modules/mathjax/fonts/HTML-CSS/TeX/eot/MathJax_Main-Regular.eot'); /* IE9 Compat Modes */
16 | src: url('/node_modules/mathjax/fonts/HTML-CSS/TeX/eot/MathJax_Main-Regular.eot?iefix') format('eot'),
17 | url('/node_modules/mathjax/fonts/HTML-CSS/TeX/woff/MathJax_Main-Regular.woff') format('woff'),
18 | url('/node_modules/mathjax/fonts/HTML-CSS/TeX/otf/MathJax_Main-Regular.otf') format('opentype'),
19 | url('/node_modules/mathjax/fonts/HTML-CSS/TeX/svg/MathJax_Main-Regular.svg#MathJax_Main-Regular') format('svg');
20 | }
21 |
22 | .mjxi {
23 | font-family: MJX_Math;
24 | font-size: 131%;
25 | line-height: .8em;
26 | }
27 |
28 | .mjx {
29 | font-family: MJX_Main;
30 | font-size: 131%;
31 | line-height: .8em;
32 | }
33 |
--------------------------------------------------------------------------------
/login/guests.js:
--------------------------------------------------------------------------------
1 | var mdb = require('../mdb');
2 |
3 | function createGuestUser( req, res, next ) {
4 | var userAgent = req.headers['user-agent'];
5 | var remoteAddress = req.headers['x-forwarded-for'] || req.connection.remoteAddress || req.socket.remoteAddress || req.connection.socket.remoteAddress;
6 |
7 | req.user = new mdb.User({
8 | isGuest: true,
9 | name: "Guest User",
10 | userAgent: userAgent,
11 | remoteAddress: remoteAddress
12 | });
13 | req.session.guestUserId = req.user._id;
14 | req.user.save(next);
15 | };
16 |
17 | // Add guest users account if not logged in.
18 | // TODO: Clean these out occasionally.
19 | module.exports.middleware = function(req, res, next) {
20 |
21 | // If we are already logged in legitimately...
22 | if (req.user) {
23 | // Then forgot this guest user nonsense.
24 | req.session.guestUserId = null;
25 | next();
26 | return;
27 | }
28 |
29 | // If we've already created a guest account...
30 | if (req.session.guestUserId) {
31 | // attempt to load it
32 | mdb.User.findOne({_id: req.session.guestUserId}, function (err, user) {
33 | if (err) {
34 | next(err);
35 | }
36 | else if (user) {
37 | req.user = user;
38 | next();
39 | }
40 | else {
41 | req.session.guestUserId = null;
42 | createGuestUser( req, res, next );
43 | }
44 | });
45 |
46 | } else {
47 | createGuestUser( req, res, next );
48 | }
49 | };
50 |
51 |
--------------------------------------------------------------------------------
/views/layouts/navbar/get-help.pug:
--------------------------------------------------------------------------------
1 | li.nav-item.dropdown
2 | a.nav-link.dropdown-toggle(id="navbarGetHelp",data-toggle="dropdown",aria-haspopup="true",aria-expanded="false")
3 | |
4 | span.hidden-md-down Get Help
5 | div.dropdown-menu(aria-labelledby="navbarGetHelp")
6 | if repositoryName && ((activity && activity.xourse && activity.xourse.path) || (xourse && xourse.path))
7 | a.dropdown-item(id="contact-instructor", href=`/${repositoryName}/${(activity ? (activity.xourse) : xourse).path}/instructors`)
8 | | Contact my instructor
9 | div.dropdown-divider
10 | if activity && activity.path && activity.title && repositoryMetadata && repositoryMetadata.github
11 | // BADBAD: instead of master, perhaps should have activity.commit
12 | a.dropdown-item(href=`https://github.com/${repositoryMetadata.github.owner}/${repositoryMetadata.github.repository}/issues/new?title=${activity.title.replace(/ +/,' ')}&body=%0A%0ASee%20[${activity.path}.tex](https://github.com/${repositoryMetadata.github.owner}/${repositoryMetadata.github.repository}/blob/master/${activity.path}.tex)`)
13 | | Report error to authors
14 | if path
15 | a.dropdown-item(href="mailto:ximera-help@osu.edu" + (path ? `?Subject=${path}` : ""))
16 | | Request help using Ximera
17 | if path
18 | a.dropdown-item(href=`https://github.com/kisonecat/ximera/issues/new?title=${path}`)
19 | | Report bug to programmers
20 |
--------------------------------------------------------------------------------
/public/stylesheets/certificate.scss:
--------------------------------------------------------------------------------
1 | /****************************************************************/
2 | dl.certificate { width: 100% }
3 |
4 | dl.certificate {
5 | display: flex;
6 | flex-flow: row;
7 | flex-wrap: wrap;
8 | width: 100%; /* set the container width*/
9 | overflow: visible;
10 | }
11 | dl.certificate dt {
12 | flex-basis: 50%;
13 | flex-grow: 1;
14 | flex-shrink: 0;
15 | text-overflow: hidden;
16 | overflow: hidden;
17 | white-space: nowrap;
18 | }
19 | dl.certificate dd {
20 | margin-left: auto;
21 | text-align: right;
22 | flex-basis: content;
23 | text-overflow: ellipsis;
24 | overflow: hidden;
25 | flex-grow: 0;
26 | flex-shrink: 1;
27 | }
28 |
29 | dl.certificate dt.page {
30 | padding-left: 1em;
31 | }
32 |
33 | dl.certificate dt.page:after, dl.certificate dt.total:after {
34 | content: ". . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ";
35 | color: $gray;
36 | }
37 |
38 |
39 | span.certificate {
40 | width: 100%;
41 | display: block;
42 | max-width: 100%;
43 | overflow: hidden;
44 | text-overflow: ellipsis;
45 | white-space: nowrap;
46 | }
47 |
48 | /******* subparagraphs and paragraphs *****/
49 | span.subparagraphHead, span.paragraphHead {
50 | margin-right: 1em;
51 | font-weight: bold;
52 | }
53 |
--------------------------------------------------------------------------------
/public/javascripts/xourse.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 | var _ = require('underscore');
3 | var Isotope = require('isotope-layout');
4 |
5 | var activityCard = require('./activity-card');
6 | var xourseIsotope = undefined;
7 |
8 | var updateSearch = function() {
9 | if (!xourseIsotope) return;
10 |
11 | var search = $('#xourse-search').val();
12 |
13 | if ((typeof search === 'undefined') || (search.length == 0)) {
14 | xourseIsotope.arrange({ filter: '*' });
15 | return;
16 | }
17 |
18 | var regexps = _.map( search.toLowerCase().split(" "), function(word) {
19 | return new RegExp(word);
20 | });
21 |
22 | xourseIsotope.arrange({
23 | filter: function() {
24 | // Bart says do not display these
25 | if ($(this).hasClass('part'))
26 | return false;
27 |
28 | var text = $(this).text().toLowerCase();
29 |
30 | return _.all( regexps, function(re) { return re.test( text ); } );
31 | }
32 | });
33 | };
34 |
35 | var layoutXourse = function( ) {
36 | var xourse = $(this);
37 |
38 | $('.activity-card', xourse).activityCard();
39 |
40 | xourse.show();
41 |
42 | var options = {
43 | layoutMode: 'fitRows',
44 | itemSelector: '.activity-card',
45 | filter: '*',
46 | animationOptions: {
47 | duration: 750,
48 | easing: 'linear',
49 | queue: false
50 | }
51 | };
52 |
53 | xourseIsotope = new Isotope( xourse.get(0),
54 | options );
55 | };
56 |
57 | // On document ready...
58 | $(function() {
59 | $('.xourse').each( layoutXourse );
60 |
61 | $('#xourse-search').on('input', function() {
62 | updateSearch();
63 | });
64 | });
65 |
66 |
--------------------------------------------------------------------------------
/public/stylesheets/activity.scss:
--------------------------------------------------------------------------------
1 | // tex4ht emits these
2 | .center {
3 | text-align: center;
4 | }
5 |
6 | // the preamble includes \newcommands which should not be displayed
7 | .preamble {
8 | display: none;
9 | }
10 |
11 | .activity-body pre {
12 | white-space: normal;
13 | }
14 |
15 | /****************************************************************/
16 | /* Try to spread out lines a bit for readability? */
17 |
18 | .activity-content {
19 | line-height: $line-height-base * 1.35;
20 | }
21 |
22 | /****************************************************************/
23 | /* LaTeX logo */
24 |
25 | span.LATEX {
26 | font-family: Times, "Times New Roman", serif;
27 | letter-spacing: 1px;
28 | }
29 |
30 | span.LATEX .A {
31 | text-transform: uppercase;
32 | letter-spacing: 1px;
33 | font-size: 0.85em;
34 | vertical-align: 0.15em;
35 | margin-left: -0.36em;
36 | margin-right: -0.15em;
37 | }
38 |
39 | span.LATEX .TEX .E {
40 | text-transform: uppercase;
41 | vertical-align: -0.5ex;
42 | margin-left: -0.1667em;
43 | margin-right: -0.125em;
44 | font-size: 1em;
45 | }
46 |
47 | // Some better spacing
48 | .sectionHead {
49 | @extend .mt-5;
50 | }
51 |
52 | .subsectionHead {
53 | @extend .mt-3;
54 | }
55 |
56 | .likesubsectionHead {
57 | @extend .mt-3;
58 | }
59 |
60 | /****************************************************************/
61 | /* tex4ht generates description lists from \begin{description} */
62 |
63 | dl.description {
64 | margin-top: 12px;
65 | margin-bottom: 12px;
66 | }
67 |
68 | dl.description dt.description {
69 | float: left;
70 | clear: left;
71 | padding-right: 0.5em;
72 | }
73 |
--------------------------------------------------------------------------------
/public/javascripts/foldable.js:
--------------------------------------------------------------------------------
1 | var $ = require('jquery');
2 | var _ = require('underscore');
3 | var database = require('./database');
4 |
5 | var buttonHtml = '';
6 |
7 | var createFoldable = function() {
8 | var foldable = $(this);
9 |
10 | var button = $(buttonHtml);
11 | foldable.before('