diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
new file mode 100644
index 0000000..dd84ea7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -0,0 +1,38 @@
+---
+name: Bug report
+about: Create a report to help us improve
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Describe the bug**
+A clear and concise description of what the bug is.
+
+**To Reproduce**
+Steps to reproduce the behavior:
+1. Go to '...'
+2. Click on '....'
+3. Scroll down to '....'
+4. See error
+
+**Expected behavior**
+A clear and concise description of what you expected to happen.
+
+**Screenshots**
+If applicable, add screenshots to help explain your problem.
+
+**Desktop (please complete the following information):**
+ - OS: [e.g. iOS]
+ - Browser [e.g. chrome, safari]
+ - Version [e.g. 22]
+
+**Smartphone (please complete the following information):**
+ - Device: [e.g. iPhone6]
+ - OS: [e.g. iOS8.1]
+ - Browser [e.g. stock browser, safari]
+ - Version [e.g. 22]
+
+**Additional context**
+Add any other context about the problem here.
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..f14c555
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: Ask a question
+ url: https://github.com/kevinlin1/just-the-class/discussions
+ about: Ask questions and discuss with other community members
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
new file mode 100644
index 0000000..bbcbbe7
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -0,0 +1,20 @@
+---
+name: Feature request
+about: Suggest an idea for this project
+title: ''
+labels: ''
+assignees: ''
+
+---
+
+**Is your feature request related to a problem? Please describe.**
+A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
+
+**Describe the solution you'd like**
+A clear and concise description of what you want to happen.
+
+**Describe alternatives you've considered**
+A clear and concise description of any alternative solutions or features you've considered.
+
+**Additional context**
+Add any other context or screenshots about the feature request here.
diff --git a/.github/workflows/jekyll.yml b/.github/workflows/jekyll.yml
new file mode 100644
index 0000000..c6cac3e
--- /dev/null
+++ b/.github/workflows/jekyll.yml
@@ -0,0 +1,20 @@
+name: Build and deploy Jekyll site to GitHub Pages
+
+on:
+ push:
+ branches:
+ - main # or master before October 2020
+
+jobs:
+ github-pages:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ - uses: helaili/jekyll-action@v2
+ with:
+ token: ${{ secrets.GITHUB_TOKEN }}
+ env:
+ CANVAS_TOKEN: ${{secrets.CANVAS_TOKEN}}
+ CANVAS_COURSE_ID: ${{secrets.CANVAS_COURSE_ID}}
+ CANVAS_BASE_URL: ${{ secrets.CANVAS_BASE_URL }}
+ JEKYLL_NO_BUNDLER_REQUIRE: true
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0042caf
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,11 @@
+*.gem
+.bundle/
+.jekyll-cache/
+.jekyll-metadata
+.sass-cache/
+Gemfile.lock
+_site/
+node_modules/
+vendor/
+*~
+.DS_Store
\ No newline at end of file
diff --git a/.netlify/build.sh b/.netlify/build.sh
new file mode 100644
index 0000000..8d2328f
--- /dev/null
+++ b/.netlify/build.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+ESCAPED_URL=$(printf '%s\n' "$DEPLOY_URL" | sed -e 's/[\/&]/\\&/g')
+
+sed -i -e "s/^url:.*$/url: '$ESCAPED_URL'/g" _config.yml
+bundle exec jekyll build -b ''
\ No newline at end of file
diff --git a/Activities/git-placeholder.txt b/Activities/git-placeholder.txt
new file mode 100644
index 0000000..2d6401c
--- /dev/null
+++ b/Activities/git-placeholder.txt
@@ -0,0 +1 @@
+Placeholder file to force git to create a folder here.
\ No newline at end of file
diff --git a/Assignments/git-fodder.txt b/Assignments/git-fodder.txt
new file mode 100644
index 0000000..7b57bd2
--- /dev/null
+++ b/Assignments/git-fodder.txt
@@ -0,0 +1 @@
+some text
diff --git a/Examples/git-placeholder.txt b/Examples/git-placeholder.txt
new file mode 100644
index 0000000..2d6401c
--- /dev/null
+++ b/Examples/git-placeholder.txt
@@ -0,0 +1 @@
+Placeholder file to force git to create a folder here.
\ No newline at end of file
diff --git a/Gemfile b/Gemfile
new file mode 100644
index 0000000..ce1f377
--- /dev/null
+++ b/Gemfile
@@ -0,0 +1,17 @@
+source "https://rubygems.org"
+gem "jekyll", "~> 3.9.0"
+gem "coderay", "~> 1.1.0"
+
+group :jekyll_plugins do
+ gem "jekyll-asciidoc", "~> 2.1.1"
+ gem "jekyll-seo-tag", "~> 2.7.1"
+ gem "jekyll-last-modified-at"
+end
+
+gem "kramdown-parser-gfm"
+
+gem "github-pages", group: :jekyll_plugins
+gem "lms-api"
+gem "just-the-docs"
+
+gem "webrick", "~> 1.7"
diff --git a/LICENSE b/LICENSE
index f09802e..5576b82 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2024 neu-se
+Copyright (c) 2023 neu-se
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..620a369
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,2 @@
+index.html:
+ bundle exec jekyll serve
diff --git a/README.md b/README.md
index 922678c..55ac2c0 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,45 @@
-# CS4530-Fall-2024
\ No newline at end of file
+# CS4530, Spring 2024: Fundamentals of Software Engineering
+This repository contains the source for the website for Northeastern's CS4530, Spring 2024 class. If you are looking to browse the site, you should visit it directly, at [https://neu-se.github.io/CS4530-Spring-2024/](https://neu-se.github.io/CS4530-Spring-2024/). If you are looking to edit the site, then you have come to the right place!
+
+### Websites for prior versions of this class:
+* [Fall 2023](https://neu-se.github.io/CS4530-Fall-2023)
+* [Spring 2023](https://neu-se.github.io/CS4530-Spring-2023)
+* [Fall 2022](https://neu-se.github.io/CS4530-Fall-2022)
+* [Spring 2022](https://neu-se.github.io/CS4530-Spring-2022)
+* [Fall 2021](https://pages.github.ccs.neu.edu/CS4530-Fall2021/CourseWebSite/)
+* [Spring 2021](https://neu-se.github.io/CS4530-CS5500-Spring-2021/) (see also [public website repo](https://github.com/neu-se/CS4530-CS5500-Spring-2021/))
+* [Fall 2020](https://pages.github.ccs.neu.edu/CS5500-CourseMaterials/CS4530-CS5500-Fall2020/)
+
+### License
+All materials in this repository (the lectures, assignments, and also the site itself) are released under the [Creative Commons Attribution-ShareAlike 4.0 License](https://creativecommons.org/licenses/by-sa/4.0/). Please feel free to reuse or remix these materials in your class. If you do, we'd love to hear your thoughts.
+
+### About this site
+This website is built using [Jekyll](https://jekyllrb.com), a lighweight static website generator that takes Markdown files as input and outputs a nicely structured website according to some template. This site uses [Kevin Lin's Just the Class](https://kevinl.info/just-the-class/) Jekyll template, which in turn is built on the [Just the Docs](https://pmarsceill.github.io/just-the-docs/) template.
+
+### Local development environment
+
+The local development environment for [Jekyll](https://jekyllrb.com) will allow you to run a live-updating local server that lets you preview what the website will look like when it is deployed. As you make changes to the website (in the markdown files), the development server will automatically update the site that it is serving. View the [Jekyll quick start guide](https://jekyllrb.com/docs/) for more information.
+
+1. Follow the GitHub documentation for [Setting up your GitHub Pages site locally with Jekyll](https://help.github.com/en/articles/setting-up-your-github-pages-site-locally-with-jekyll).
+
+1. Install a local Jekyll server if you do not already have one. To do this, say
+```bash
+$ gem install bundler jekyll
+$ bundle install
+```
+1. Start your local Jekyll server.
+```bash
+$ bundle exec jekyll serve
+```
+1. Point your web browser to [http://localhost:4000](http://localhost:4000)
+1. Reload your web browser after making a change to preview its effect.
+
+### Canvas Sync
+The GitHub Actions build system for this site is configured to automatically push content into Canvas when changes are pushed to the main branch of this repository. Specifically, it will push the assignments, lectures, and the home page material into Canvas, overwriting any changes made to those assignments, modules, or home page content in Canvas.
+
+The Canvas sync plugin benefits from some metadata on assignments (e.g. due date) and lectures (e.g. list of lessons and curricular materials). Hence, the assignments and lectures in this site include a few extra front-matter definitions at the top of each page.
+
+To use this plugin on a new repository, configure the GitHub secrets `CANVAS_BASE_URL`, `CANVAS_COURSE_ID` and `CANVAS_TOKEN`. Given that the plugin is embeded directly in the project (and not in a separate gem), it's also necessary to set the environmental variable `JEKYLL_NO_BUNDLER_REQUIRE` to true. Ideally, after this course offering is complete, the plugin should be extracted to its own module for better reusability. Note that GitHub Pages limits which plugins can be built using the standard GitHub Pages builder, and hence, the canvas sync plugin won't work if built using GitHub Pages. However, GitHub *Actions* will build whatever you want, including Jekyll sites with custom plugins. See the GitHub Actions configuration in this repo.
+
+### Automatic website previews
+This repository is also built by [Netlify](https://www.netlify.com). We do not use Netlify to host the production site, but use it for development. Each commit to this repository (to any branch) is automatically deployed to a publicly-accessible web address, allowing for an easy preview/review of what the site will look like given some change. The easiest way to use this workflow is to create a pull request: Netlify will add a status message on the pull request with a link to the current deploy preview of the site as it is in that pull request. As you push more commits to the pull request, Netlify will continue to update the site. Alternatively, you can log into the [Netlify control panel using GitHub sign-in](https://app.netlify.com/sites/objective-mclean-ad778c/overview) and view the complete deployment history, with direct links to all deploy previews (not just for pull requests).
diff --git a/Slides/git-placeholder.txt b/Slides/git-placeholder.txt
new file mode 100644
index 0000000..2d6401c
--- /dev/null
+++ b/Slides/git-placeholder.txt
@@ -0,0 +1 @@
+Placeholder file to force git to create a folder here.
\ No newline at end of file
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..a2e69f3
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1,83 @@
+# Welcome to Jekyll!
+#
+# This config file is meant for settings that affect your whole site, values
+# which you are expected to set up once and rarely edit after that. If you find
+# yourself editing these this file very often, consider using Jekyll's data files
+# feature for the data you need to update frequently.
+#
+# For technical reasons, this file is *NOT* reloaded automatically when you use
+# 'jekyll serve'. If you change this file, please restart the server process.
+
+# Site settings
+# These are used to personalize your new site. If you look in the HTML files,
+# you will see them accessed via {{ site.title }}, {{ site.github_repo }}, and so on.
+# You can create any custom variable you would like, and they will be accessible
+# in the templates via {{ site.myvariable }}.
+title: CS4530, Fall 2024
+#email: your-email@example.com
+description: >- # this means to ignore newlines until "baseurl:"
+ Northeastern University CS 4530 "Fundamentals of Software Engineering",
+ Fall 2024. Professors Adeel Bhutta and Mitch Wand.
+tagline: Fundamentals of Software Engineering
+baseurl: '/CS4530-Fall-2024' # the subpath of your site, e.g. /blog
+url: 'https://neu-se.github.io' # the base hostname & protocol for your site, e.g. http://example.com
+exclude: ["Gemfile", "Gemfile.lock", "LICENSE", "README.md", "disabled", "vendor", "_plugins", "temp"]
+
+# Theme settings
+theme: just-the-docs
+color_scheme: neu
+search_enabled: true
+heading_anchors: true
+permalink: pretty
+
+callouts:
+ warning:
+ title: Warning
+ color: red
+
+#aux_links:
+# Kevin Lin:
+# - 'https://kevinl.info'
+# Just the Class on GitHub:
+# - 'https://github.com/kevinlin1/just-the-class'
+footer_content: "© 2024 Adeel Bhutta and Mitch Wand. Released under the CC BY-SA license"
+
+
+# Collections for website data
+collections:
+ staffers:
+ modules:
+ announcements:
+# Default layouts for each collection type
+defaults:
+ - scope:
+ path: ''
+ type: staffers
+ values:
+ layout: staffer
+ subpath: '/assets/images/'
+ - scope:
+ path: ''
+ type: modules
+ values:
+ layout: module
+ - scope:
+ path: ''
+ type: announcements
+ values:
+ layout: announcement
+
+compress_html:
+ clippings: all
+ comments: all
+ endings: all
+ startings: []
+ blanklines: false
+ profile: false
+
+plugins:
+ - jekyll-seo-tag
+ - jekyll-last-modified-at
+
+last-modified-at:
+ date-format: '%b %d, %y %H:%M %Z'
diff --git a/_data/config.yml b/_data/config.yml
new file mode 100644
index 0000000..e257367
--- /dev/null
+++ b/_data/config.yml
@@ -0,0 +1,33 @@
+deadlines:
+ '3':
+ Wed:
+ date: 2024/01/24 11:00am
+ content: ip1
+ '4':
+ Wed:
+ date: 2024/01/31 11:00am
+ content: tp1
+ '6':
+ Wed:
+ date: 2024/02/14 11:00am
+ content: tp2
+ '7':
+ Wed:
+ date: 2024/02/21 11:00am
+ content: ip2
+ '8':
+ Wed:
+ date: 2024/02/28 11:00am
+ content: tp3
+ '14':
+ Wed:
+ date: 2024/04/10 11:00am
+ content: tp4
+dates:
+ firstMonday: 2024/01/08
+ numWeeks: 15
+types:
+ default:
+ className: lesson
+ holiday:
+ className: holiday
\ No newline at end of file
diff --git a/_data/modules.yml b/_data/modules.yml
new file mode 100644
index 0000000..5e2b2a3
--- /dev/null
+++ b/_data/modules.yml
@@ -0,0 +1,139 @@
+# Format of this document:
+# Each top-level key is an item that is referneced by any section schedule's "content" array
+# Each key has a "name" attribute (rendered on the calendar), a "page" attribute (which contains a link to the lesson) and an optional "type" attribute, which is a key into the _data/config.yml "types" dictionary.
+1a:
+ name: Orientation & Requirements
+ page:
+1b:
+ name: Typescript & Test Driven Development
+ page:
+2a:
+ name: Test Adequacy
+ page:
+2b:
+ name: Code-Level Design Principles
+ page:
+3a:
+ name: Interaction-Level Design Patterns
+ page:
+3b:
+ name: Concurrency Patterns
+ page:
+4a:
+ name: Software Processes & Agile
+ page:
+4b:
+ name: React Part 1 (Introduction)
+ page:
+5a:
+ name: React Part 2 (Hooks)
+ page:
+5b:
+ name: Distributed System Architectures (Part 1)
+ page:
+6a:
+ name: Distributed System Architectures (Part 2)
+ page:
+6b:
+ name: Tests with Larger Scope
+ page:
+7a:
+ name: Principles for Cloud Deployment
+ page:
+7b:
+ name: Continuous Development
+ page:
+8a:
+ name: Work on project (Discuss project plan/revisions)
+8b:
+ name: Midterm Review
+9a:
+ name: Midterm
+9b:
+ name: Security
+ page:
+10a:
+ name: Work on project
+10b:
+ name: Technical Debt
+ page:
+guest-pm:
+ name: Product Management (with special guest, Prof Katie Stolee)
+ page:
+11a:
+ name: Work on Project
+11b:
+ name: Open Source
+ page:
+12a:
+ name: Work on Project
+12b:
+ name: Ethics
+ page:
+12c:
+ name: Work on Project / Mini-Demo
+13a:
+ name: Work on Project
+13b:
+ name: Presentations
+14a:
+ name: Presentations
+pitch:
+ name: Elevator Pitch
+posters:
+ name: Poster Presentations
+advanced:
+ name: Advanced Topics in SE
+ip1:
+ name: Individual Project Deliverable 1
+ type: deadline
+ page:
+ip2:
+ name: Individual Project Deliverable 2
+ type: deadline
+ page:
+tp1:
+ name: Project Formation
+ type: deadline
+tp2:
+ name: Preliminary Project Plan
+ type: deadline
+ page:
+tp3:
+ name: Revised Project Plan
+ type: deadline
+ page:
+tp4:
+ name: Project Final Deliverable
+ type: deadline
+ page:
+veteransday:
+ name: Veteran's Day (No class)
+ type: holiday
+thanksgiving:
+ name: Thanksgiving Break (No class)
+ type: holiday
+indigenous:
+ name: Indigenous Peoples Day (No class)
+ type: holiday
+mlkday:
+ name: MLK Day (No class)
+ type: holiday
+presidentsday:
+ name: President's Day (No class)
+ type: holiday
+springbreak:
+ name: Spring Break (No class)
+ type: holiday
+patriotsday:
+ name: Patriots Day (No class)
+ type: holiday
+teamMeetings:
+ name: Team Meetings
+midtermReview:
+ name: Midterm Review
+thanksgivingNoClass:
+ name: Thanksgiving (No class)
+ type: holiday
+noClass:
+ name: No Class
diff --git a/_includes/lesson.html b/_includes/lesson.html
new file mode 100644
index 0000000..0576596
--- /dev/null
+++ b/_includes/lesson.html
@@ -0,0 +1,11 @@
+### Lecture Slides:
+
+{% for lesson in page.lessons %}
+ - {{ lesson.title }}
+ {% if lesson.pdf %}
+ Slides PDF,
+ {% endif %}
+ PPT
+
+{% endfor %}
+
diff --git a/_includes/minutes.liquid b/_includes/minutes.liquid
new file mode 100644
index 0000000..c3207ae
--- /dev/null
+++ b/_includes/minutes.liquid
@@ -0,0 +1,20 @@
+{% capture _minutes_workspace %}
+ {% comment %}
+ Return the number of minutes between midnight and the given time string (e.g. '9:30 AM').
+
+ Parameters:
+ `time` (string): the time to convert.
+ {% endcomment %}
+
+ {% assign _time = include.time %}
+ {% assign _hhmm = _time | split: ' ' | first | split: ':' %}
+ {% assign _hours = _hhmm | first | to_integer %}
+ {% assign _minutes = _hhmm | last | to_integer %}
+ {% assign _ampm = _time | split: ' ' | last | upcase %}
+
+ {% if _ampm == 'AM' and _hours == 12 %}
+ {% assign _hours = _hours | minus: 12 %}
+ {% elsif _ampm == 'PM' and _hours != 12 %}
+ {% assign _hours = _hours | plus: 12 %}
+ {% endif %}
+{% endcapture %}{% assign _minutes_workspace = '' %}{{ _hours | times: 60 | plus: _minutes }}
diff --git a/_includes/officeHours.html b/_includes/officeHours.html
new file mode 100644
index 0000000..6f48adb
--- /dev/null
+++ b/_includes/officeHours.html
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/_layouts/announcement.html b/_layouts/announcement.html
new file mode 100644
index 0000000..3451611
--- /dev/null
+++ b/_layouts/announcement.html
@@ -0,0 +1,14 @@
+
+
{{ page.title }}
+
+ {% if page.date %}
+ {{ page.date | date: '%b %e' }}
+ ·
+ {% endif %}
+ {% assign minutes = content | strip_html | number_of_words | divided_by: 180.0 | round %}
+ {{ minutes }} min read
+
+
+ {{ content }}
+
+
diff --git a/_layouts/assignment.html b/_layouts/assignment.html
new file mode 100644
index 0000000..287c5f3
--- /dev/null
+++ b/_layouts/assignment.html
@@ -0,0 +1,14 @@
+---
+layout: default
+---
+
+ {% if page.long_title %}
+ {{ page.long_title }}
+ {% else %}
+ {{page.title}}
+ {% endif %}
+ Due {{ page.due_date }}
+
+
+
+{{ content }}
\ No newline at end of file
diff --git a/_layouts/home.html b/_layouts/home.html
new file mode 100644
index 0000000..0c9dd44
--- /dev/null
+++ b/_layouts/home.html
@@ -0,0 +1,7 @@
+---
+layout: default
+---
+
+Last updated: {{ page.last_modified_at | date: '%b %d, %y %H:%M %Z' }} |
Permalink
+
+{{ content }}
diff --git a/_layouts/minimal.html b/_layouts/minimal.html
new file mode 100644
index 0000000..5c70640
--- /dev/null
+++ b/_layouts/minimal.html
@@ -0,0 +1,124 @@
+---
+layout: table_wrappers
+---
+
+
+
+
+{% include head.html %}
+
+
+
+ {% capture nav %}
+ {% if site.just_the_docs.collections %}
+ {% assign collections_size = site.just_the_docs.collections | size %}
+ {% for collection_entry in site.just_the_docs.collections %}
+ {% assign collection_key = collection_entry[0] %}
+ {% assign collection_value = collection_entry[1] %}
+ {% assign collection = site[collection_key] %}
+ {% if collection_value.nav_exclude != true %}
+ {% include nav.html pages=collection %}
+ {% endif %}
+ {% endfor %}
+ {% else %}
+ {% include nav.html pages=site.html_pages %}
+ {% endif %}
+ {% endcapture %}
+
+
+ {% unless page.url == "/" %}
+ {% if page.parent %}
+ {%- for node in pages_list -%}
+ {%- if node.parent == nil -%}
+ {%- if page.parent == node.title or page.grand_parent == node.title -%}
+ {%- assign first_level_url = node.url | absolute_url -%}
+ {%- endif -%}
+ {%- if node.has_children -%}
+ {%- assign children_list = pages_list | where: "parent", node.title -%}
+ {%- for child in children_list -%}
+ {%- if page.url == child.url or page.parent == child.title -%}
+ {%- assign second_level_url = child.url | absolute_url -%}
+ {%- endif -%}
+ {%- endfor -%}
+ {%- endif -%}
+ {%- endif -%}
+ {%- endfor -%}
+
+ {% endif %}
+ {% endunless %}
+
+ {% if site.heading_anchors != false %}
+ {% include vendor/anchor_headings.html html=content beforeHeading="true" anchorBody="
" anchorClass="anchor-heading" %}
+ {% else %}
+ {{ content }}
+ {% endif %}
+
+ {% if page.has_children == true and page.has_toc != false %}
+
+
Table of contents
+
+ {%- assign children_list = pages_list | where: "parent", page.title | where: "grand_parent", page.parent -%}
+ {% for child in children_list %}
+ -
+ {{ child.title }}{% if child.summary %} - {{ child.summary }}{% endif %}
+
+ {% endfor %}
+
+ {% endif %}
+
+ {% capture footer_custom %}
+ {%- include footer_custom.html -%}
+ {% endcapture %}
+ {% if footer_custom != "" or site.last_edit_timestamp or site.gh_edit_link %}
+
+
+ {% endif %}
+
+
+
+
+
diff --git a/_layouts/module.html b/_layouts/module.html
new file mode 100644
index 0000000..c0a7fb9
--- /dev/null
+++ b/_layouts/module.html
@@ -0,0 +1,9 @@
+---
+layout: default
+---
+
+Last updated: {{ page.last_modified_at | date: '%b %d, %y %H:%M %Z' }} |
Permalink
+{{ page.title }}
+
+ {{ content }}
+
diff --git a/_layouts/page.html b/_layouts/page.html
new file mode 100644
index 0000000..0c9dd44
--- /dev/null
+++ b/_layouts/page.html
@@ -0,0 +1,7 @@
+---
+layout: default
+---
+
+Last updated: {{ page.last_modified_at | date: '%b %d, %y %H:%M %Z' }} |
Permalink
+
+{{ content }}
diff --git a/_layouts/schedule.html b/_layouts/schedule.html
new file mode 100644
index 0000000..37d8a34
--- /dev/null
+++ b/_layouts/schedule.html
@@ -0,0 +1,90 @@
+---
+layout: default
+---
+
+{{ page.title }}
+
+{{page.instructor}}
+{{page.dates}}, {{page.location}}
+
+{% assign days = "Mon,Tue,Wed,Thu,Fri" | split: "," %}
+{% for wk in (1..site.data.config.dates.numWeeks) %}
+ {% capture wID %}{{ wk }}{% endcapture %}
+
+ Week {{wID}} |
+ {% assign week=page.weeks[wID] %}
+ {% for day in days %}
+
+ {% if site.data.config.deadlines[wID][day] or week[day] %}
+
+ {% if site.data.config.deadlines[wID][day] %}
+ {% assign c = site.data.config.deadlines[wID][day] %}
+ {{ c.date | date: "%a, %b %d" }}
+
+ {% assign m = site.data.modules[c.content] %}
+ {% if m.page %}
+ {{ m.name }}
+ {% else %}
+ {{ m.name }}
+ {% endif %}
+ Due at {{ c.date | date: "%I:%M %P" }}
+
+ {% elsif week[day] %}
+ {{ week[day].date | date: "%a, %b %d" }}
+ {% endif %}
+ {% for c in week[day].content %}
+ {% assign m = site.data.modules[c] %}
+ {% assign className = site.data.sections.types.default.className %}
+ {% if site.data.config.types[m.type] %}
+ {% assign className = site.data.config.types[m.type].className %}
+ {% endif %}
+
+ {% if m.page %}
+ {{ m.name }}
+ {% else %}
+ {{ m.name }}
+ {% endif %}
+ {{ week[day].note | markdownify }}
+
+ {% endfor %}
+
+ {% endif %}
+ |
+ {% endfor %}
+
+{% endfor %}
+
+{% assign start_time = page.timeline | first %}
+{% capture offset %}{% include minutes.liquid time=start_time %}{% endcapture %}
+
+
+ {% for time in page.timeline %}
+ - {{ time }}
+ {% endfor %}
+
+
+ {% for day in page.schedule %}
+ -
+
+ {% if day.events %}
+
+ {% endif %}
+
+ {% endfor %}
+
+
diff --git a/_layouts/staffer.html b/_layouts/staffer.html
new file mode 100644
index 0000000..d0e6356
--- /dev/null
+++ b/_layouts/staffer.html
@@ -0,0 +1,27 @@
+
+ {% if page.photo %}
+
+ {% endif %}
+
+
+ {% if page.website %}
+ {{ page.name }}
+ {% else %}
+ {{ page.name }}
+ {% endif %}
+ {% if page.pronouns %}
+ {{ page.pronouns }}
+ {% endif %}
+
+ {% if page.email %}
+
{{ page.email }}
+ {% endif %}
+ {% if page.section %}
+
Section: {{ page.section | markdownify | strip_html }}
+ {% endif %}
+ {% if page.office-hours %}
+
Office Hours: {{ page.office-hours | markdownify }}
+ {% endif %}
+ {{ content }}
+
+
diff --git a/_plugins/canvas-lms.rb b/_plugins/canvas-lms.rb
new file mode 100644
index 0000000..a9b94d0
--- /dev/null
+++ b/_plugins/canvas-lms.rb
@@ -0,0 +1,260 @@
+require 'lms_api'
+
+class Authentication
+ @@token = "string"
+
+ def initialize(token)
+ @token = token
+ end
+
+ def token()
+ return @token
+ end
+end
+
+class CanvasSyncer
+ @api
+ @course_id
+ @modules
+ @assignments
+
+ def initialize()
+ @course_id = ENV['CANVAS_COURSE_ID']
+ auth = Authentication.new(ENV['CANVAS_TOKEN'])
+ LMS::Canvas.auth_state_model = Authentication
+ @api = LMS::Canvas.new(ENV['CANVAS_BASE_URL'], auth)
+ end
+
+ def getAllAssignments()
+ if (!@assignments)
+ params = {
+ course_id: @course_id,
+ all_dates: true,
+ }
+ url = LMS::Canvas.lms_url("LIST_ASSIGNMENTS_ASSIGNMENTS", params)
+ @assignments = @api.api_get_all_request(url)
+ end
+ return @assignments
+ end
+
+ def getModuleContent(moduleID)
+ params = {
+ module_id: moduleID,
+ course_id: @course_id,
+ all_dates: true,
+ }
+ url = LMS::Canvas.lms_url("LIST_MODULE_ITEMS", params)
+ return @api.api_get_all_request(url)
+ end
+
+ def getAllModules()
+ if (!@modules)
+ params = {
+ course_id: @course_id,
+ all_dates: true,
+ }
+ url = LMS::Canvas.lms_url("LIST_MODULES", params)
+ @modules = @api.api_get_all_request(url)
+ end
+ return @modules
+ end
+
+ def getOrCreateModuleByName(name, sort_order)
+ self.getAllModules().each do |mod|
+ if (mod['name'] == name)
+ return mod['id']
+ end
+ end
+ params = {
+ course_id: @course_id,
+ module: { name: name, position: sort_order }
+ }
+ url = LMS::Canvas.lms_url("CREATE_MODULE", params)
+ print "Canvas importer: Creating canvas module " + name + "\n"
+ ret = @api.api_post_request(url, module: { name: name, position: sort_order })
+ params = {
+ course_id: @course_id,
+ id: ret['id'],
+ }
+ url = LMS::Canvas.lms_url("UPDATE_MODULE", params)
+ @api.api_put_request(url, module: { published: true })
+
+ return ret['id']
+ end
+
+ def createModuleItem(modID, type, title, position, indent, url)
+ module_item = {
+ title: title,
+ type: type,
+ position: position,
+ indent: indent,
+ published: true,
+ content_id: "none"
+ }
+ if (url != nil)
+ module_item['new_tab'] = true
+ module_item['external_url'] = url
+ end
+ params = {
+ course_id: @course_id,
+ module_id: modID,
+ module_item: module_item
+ }
+ url = LMS::Canvas.lms_url("CREATE_MODULE_ITEM", params)
+ print "Creating module item '" + title + "'\n"
+ ret = @api.api_post_request(url, module_item: module_item)
+
+ params = {
+ course_id: @course_id,
+ module_id: modID,
+ id: ret['id'],
+ }
+ url = LMS::Canvas.lms_url("UPDATE_MODULE_ITEM", params)
+ ret = @api.api_put_request(url, module_item: {published: true})
+ end
+
+ #https://canvas.instructure.com/doc/api/modules.html#method.context_module_items_api.create
+ $moduleComponents = {}
+ $moduleComponents['title'] = { type: "SubHeader" }
+ $moduleComponents['pdf'] = { type: "ExternalURL", name: "Slides (PDF)" }
+ $moduleComponents['ppt'] = { type: "ExternalURL", name: "Slides (PPT)" }
+
+ def syncModule(modName, sort_order, contents, tutorials, siteBaseURL, pageURL)
+ modID = getOrCreateModuleByName(modName, sort_order)
+ canvasItems = getModuleContent(modID)
+ ctr = 1
+ contents.each do |ourItem|
+ ourItem.each_key do |itemKey|
+ if itemKey == "title"
+ canvasIdx = canvasItems.index { |item| item['title'] == ourItem['title'] }
+ if canvasIdx != nil
+ # TODO - is there anything here to update/check?
+ else
+ #create the text item
+ self.createModuleItem(modID, "SubHeader", ourItem['title'], ctr, 0, nil)
+ end
+ else
+ file = CGI.escape ourItem[itemKey]
+ file = file.gsub(/\+/, '%20')
+ url = siteBaseURL + "/Slides/" + file
+ canvasIdx = canvasItems.index { |item| item['external_url'] == url }
+ if canvasIdx != nil
+ cItem = canvasItems[canvasIdx]
+ # TODO - is there anything here to update/check?
+ else
+ #create the url link
+ self.createModuleItem(modID, "ExternalURL", $moduleComponents[itemKey][:name], ctr, 1, url)
+ end
+ end
+ ctr = ctr + 1
+ end
+ # also add a link to the module on the webpage
+ if (pageURL[0] == '/')
+ pageURL = siteBaseURL + pageURL
+ end
+ canvasIdx = canvasItems.index { |item| item['external_url'] == pageURL }
+ if canvasIdx == nil
+ self.createModuleItem(modID, "ExternalURL", "Additional content on the course webpage", ctr, 1, pageURL)
+ end
+ ctr = ctr + 1;
+ end
+ end
+
+ def syncAssignment(title, dueDate, contents, baseURL, lastModifiedAt, permalink)
+ assignmentIdx = getAllAssignments().index { |assignment| assignment['name'] == title }
+ dueDate = DateTime.parse(dueDate)
+ # baseURL = baseURL[0..baseURL.rindex('/')]
+ contents = 'This content was last updated at:' + lastModifiedAt + '; you may view it with native formatting
on the course website ' + contents
+
+ contents = contents.gsub(//, '')
+ cleanContent = cleanContent.gsub(/