Making and publishing your first course using markdown2canvas

A course that uses markdown2canvas is just a folder on your computer, with a bit of extra structure. (Ideally, you should turn it into a git repository and use version control.)

The folder has some special structure. I’ll take you through it in stages. You can also copy the directory from the examples/ folder in the repo if you don’t want to go through it in steps.

Create a course folder

Courses in markdown2canvas are contained in a folder. The name of the folder is arbitrary. 🎯 Make a folder called course_folder. Call it whatever you want, but below I’ll call it course_folder.

Create the course metadata

🎯 Make a folder called _course_metadata. The name of this folder matters. (I use leading underscores to denote things never published or that aren’t Canvas content.)

🎯 Make two files therein, defaults.json and replacements.json. Here’s a picture of the directory structure after this step:

course_folder/
course_folder/_course_metadata/                   # must be named _course_metadata
course_folder/_course_metadata/defaults.json      # must be named defaults.json
course_folder/_course_metadata/replacements.json  # <--- replacements.json is arbitrary.  change in `defaults.json`

🎯 Here’s some starter contents for you to copy into those two files.

defaults.json:

{
	"style": "_styles/generic",
	"replacements": "_course_metadata/replacements.json"
}

replacements.json:

{
	"$REPLACETHISTEXT": "with this text",
	"a source replacement with spaces": "<iframe width=\"560\" height=\"315\" src=\"https://www.youtube.com/embed/dQw4w9WgXcQ?si=BqTm4nbZOLTHaxnz\" title=\"YouTube video player\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" allowfullscreen></iframe>",
	"another source replacement with spaces": "destination_without_spaces"
}

Ignore generated files (git)

At this time, markdown2canvas uses in-place builds, sorry. That is, it pollutes as it publishes, and makes two files in content folders: styled_source.md and result.html. Best to make version control ignore them!

🎯 Add file .gitignore to root level.

course_folder/.gitignore:

*downloaded_content/
*.log
.DS_Store
result.html
styled_source.md
*.icloud

Add a default style

I urge you to use the styling system, in which case it probably looks like this:

course_folder/
course_folder/_course_metadata/
course_folder/_course_metadata/defaults.json
course_folder/_course_metadata/replacements.json

course_folder/_styles/generic/              # generic is arbitrary.  change in `defaults.json`
course_folder/_styles/generic/header.html
course_folder/_styles/generic/header.md
course_folder/_styles/generic/footer.md
course_folder/_styles/generic/footer.html

🎯 Just make those four files blank for now.

Add a page and an assignment

Let’s add some content. 🎯 Add a page and an assignment.

course_folder/
course_folder/_course_metadata/...

course_folder/_styles/generic/...

# the below paths are arbitrarily named
course_folder/Lesson1/readings.page/            # a folder.  i use suffixes to indicate type, but markdown2canvas is ignorant of them
course_folder/Lesson1/readings.page/meta.json   # necessary for all containerized content
course_folder/Lesson1/readings.page/source.md   # necessary for pages and assignments

course_folder/Lesson1/assignment1.assignment
course_folder/Lesson1/assignment1.assignment/meta.json   # necessary for all containerized content
course_folder/Lesson1/assignment1.assignment/source.md   # necessary for pages and assignments

meta.json

The meta.json files vary per content type, and by your needs. 🎯 Let’s make them for these two pieces of content:

readings.page:

{
    "type": "page",
    "name": "Readings for Lesson 1",
    "modules":["Lesson 1"]
}

assignment1.assignment:

{
	"type": "assignment",
	"points_possible": 100,
	"allowed_extensions": ["pdf","docx","jpg"],
	"submission_types":["online_upload"],
	"name": "Assignment 1",
	"modules": ["Lesson 1"]
}

source.md

Pages and Assignments must have a source.md file. It’s markdown, and can include html, too.

🎯 Write whatever markdown you want in the two source.md files. I gave you some terrible starter, but at least the assignment source includes a demo of the text replacement system.

readings.page:

This is a page of readings for Lesson 1.

## Topic 1

some text here.

:open_book: <--- an emoji shortcode

## Topic 2

arsoitn

* A markdown list
* second item

a markdown [link](wikipedia.org)

assignment1.assignment:

This is the first assignment in the course.


## This assignment's description includes some fancy features of `markdown2canvas`

[link to readings for this Lesson](page:Readings for Lesson 1)  <---- a link to another page.  Will be populated only if the page is already published.  I generally publish my whole course twice at the beginning of a term, just like compiling LaTeX to get references and page numbers right.

[An embedded file link](file:ring.stl)

A text replacement will happen $REPLACETHISTEXT.  Note that the dollar sign is NOT special -- it's only special because I used it in a key of the `replacements.json` file.

This text here will get replaced: a source replacement with spaces

Add a file for students to download

We’re going to make a link to a file in the module for Lesson 1, and we also make a link to this file in the assignment we created. Here’s the new structure after this step:

course_folder/
course_folder/_course_metadata/...

course_folder/_styles/generic/...

course_folder/Lesson1/readings.page/...
course_folder/Lesson1/assignment1.assignment/...

course_folder/Lesson1/ring.stl
course_folder/Lesson1/ring.file/
course_folder/Lesson1/ring.file/meta.json

🎯 Make a file in Lesson1/ called ring.stl. The contents of this file are arbitrary.

🎯 Make a new folder in Lesson1, and call it ring.file. Make a file in there called meta.json. Here are some contents for you to copy-paste.

ring.file/meta.json

{
	"type":"file",
	"title":"a torus: ring.stl",
	"filename":"../ring.stl",
	"modules":["Lesson 1","Course data files"],
	"destination": "automatically_uploaded_files/a_subfolder"
}

Tools to publish content

I use a script to help me publish my content. 🎯 Let’s add it:

course_folder/
course_folder/_course_metadata/...
course_folder/_styles/generic/...
course_folder/Lesson1/...

course_folder/_tools/publish_ready_content.py   # loops over `content_ready` and publishes to course
course_folder/_tools/content_ready          # names of content folders ready to publish
course_folder/_tools/content_all            # a txt file with names of content folders

Here’s a script I use in DS710. 🎯 Copy-paste it.

#!/bin/python3

# a script for publishing content that's ready to go!
# this script should be executed from root level in this repo.

dry_run = False

import markdown2canvas as mc

# we will skip blank lines and lines that start with # or %
with open('_tools/content_ready','r') as f:
	ready_files = f.read().split('\n')

ready_files = [f'{f}'.strip() for f in ready_files if f and not (f.startswith('#') or f.startswith('%'))]

print(ready_files)

# gets the canvas_url
canvas_url = "https://uweau.instructure.com/" # 🎯 REPLACE WITH YOUR URL

# a list of course_ids, in case have multiple courses published to. 
course_ids = [705022] # 🎯 REPLACE WITH YOUR NUMBER!!!!!!!!!!!!!!!!!

canvas = mc.make_canvas_api_obj(url=canvas_url)

for course_id in course_ids:
	course = canvas.get_course(course_id) 

	print(f'publishing to {course.name}')

	# a helper function to make the correct object from the extension of the content folder
	def make_mc_obj(f):
		if f.endswith('page'):
			return mc.Page(f)
		if f.endswith('assignment'):
			return mc.Assignment(f)
		if f.endswith('link'):
			return mc.Link(f)
		if f.endswith('file'):
			return mc.File(f)

	# loop over the files
	for f in ready_files:
		print(f)
		obj = make_mc_obj(f)

		if not dry_run:
			obj.publish(course, overwrite=True)
		else:
			print(f'[dry run] publishing {obj}')

🎯 Let’s also list the content as ready to publish in content_ready:

# content for Lesson 1

Lesson1/readings.page

# publish this before the assignment so that it gets linked when the assignment is published.
Lesson1/ring.file 

Lesson1/assignment1.assignment

Note that you just list the folder, and markdown2canvas does all the work with meta.json and source.md.

Publish the content!!!!!

Now, assuming you’ve completed the setup steps (Mac/Linux: saving your API key and URL in a .py file, and specifying the name of the file via an environment variable called CANVAS_CREDENTIALS_FILE), you should be able to publish the content to your course.

🎯 Be sure you copied in your Canvas course number to the _tools/publish_ready_content.py script!

🎯 From course root level, run

python _tools/publish_ready_content.py

and your content should publish to Canvas. Easy peasy!