## Description ## Rubric ## Instructor notes ## Submissions ```dataview TABLE WITHOUT ID ("[[" + file.name + "]]") AS "Submission", people AS "Student", date AS "Graded", (score + "/" + max_score) AS "Score", status AS "Status" FROM "Activities/Submissions" WHERE contains(assignments, this.file.link) SORT score DESC ``` ## Submission summary ```dataview TABLE WITHOUT ID length(rows) AS "Count", round(average(rows.score), 1) AS "Avg", min(rows.score) AS "Min", max(rows.score) AS "Max" FROM "Activities/Submissions" WHERE contains(assignments, this.file.link) AND score GROUP BY true ``` ## Actions ```dataviewjs const btn = dv.el("button", "+ New task from this note"); btn.addEventListener("click", async () => { window._pendingTaskSource = dv.current().file.name; const templater = app.plugins.plugins["templater-obsidian"]; if (!templater) { new Notice("Templater plugin not enabled"); return; } const taskTpl = app.vault.getAbstractFileByPath("Templates/Task.md"); if (!taskTpl) { new Notice("Templates/Task.md not found"); return; } await templater.templater.create_new_note_from_template(taskTpl, "", "", true); }); ``` ## Open tasks ```dataviewjs const dim = "assignments"; const tasks = dv.pages('"Activities/Tasks"') .where(p => { if (p.status !== "open") return false; const val = p[dim]; if (!val) return false; const arr = Array.isArray(val) ? val : [val]; return arr.some(d => d && d.path === dv.current().file.path); }) .sort(p => p.due, "asc"); const rows = tasks.map(p => { const cb = document.createElement("input"); cb.type = "checkbox"; cb.addEventListener("click", async () => { const file = app.vault.getAbstractFileByPath(p.file.path); if (!file) return; const content = await app.vault.read(file); const today = new Date().toISOString().slice(0, 10); let updated = content.replace(/^status:\s*["']?open["']?\s*$/m, "status: done"); if (/^closed:/m.test(updated)) { updated = updated.replace(/^closed:.*$/m, `closed: ${today}`); } else { updated = updated.replace(/^(---\n[\s\S]*?\n)---\n/, `$1closed: ${today}\n---\n`); } if (updated !== content) await app.vault.modify(file, updated); }); return [cb, p.file.link, p.due, p.date]; }); dv.table(["Done", "Task", "Due", "Created"], rows); ``` ## Closed tasks ```dataviewjs const dim = "assignments"; const tasks = dv.pages('"Activities/Tasks"') .where(p => { if (p.status !== "done") return false; const val = p[dim]; if (!val) return false; const arr = Array.isArray(val) ? val : [val]; return arr.some(d => d && d.path === dv.current().file.path); }) .sort(p => p.closed, "desc"); const rows = tasks.map(p => { const cb = document.createElement("input"); cb.type = "checkbox"; cb.checked = true; cb.addEventListener("click", async () => { const file = app.vault.getAbstractFileByPath(p.file.path); if (!file) return; const content = await app.vault.read(file); let updated = content.replace(/^status:\s*["']?done["']?\s*$/m, "status: open"); updated = updated.replace(/^closed:.*$/m, "closed: "); if (updated !== content) await app.vault.modify(file, updated); }); return [cb, p.file.link, p.closed, p.due]; }); dv.table(["Reopen", "Task", "Closed", "Due"], rows); ``` ## Related activity ```dataviewjs const dim = "assignments"; const all = dv.pages('"Activities"') .where(p => { const v = p[dim]; if (!v) return false; const arr = Array.isArray(v) ? v : [v]; return arr.some(l => l && l.path === dv.current().file.path); }) .sort(p => p.date, "desc") .filter(p => p.type !== "submission"); // ===== Controls ===== const controls = dv.el("div", "", { attr: { style: "display: flex; gap: 1em; align-items: center; margin-bottom: 0.75em; flex-wrap: wrap;" } }); const typeWrap = controls.createEl("label", { attr: { style: "display: inline-flex; align-items: center; gap: 0.4em;" } }); typeWrap.createSpan({ text: "Type:" }); const typeSelect = typeWrap.createEl("select"); const typeOptions = ["All", ...[...new Set(all.map(p => p.type).filter(Boolean))].sort()]; for (const t of typeOptions) typeSelect.createEl("option", { text: t, value: t }); const rangeWrap = controls.createEl("label", { attr: { style: "display: inline-flex; align-items: center; gap: 0.4em;" } }); rangeWrap.createSpan({ text: "Range:" }); const rangeSelect = rangeWrap.createEl("select"); const ranges = [ { label: "All time", value: "all" }, { label: "Last 7 days", value: "7" }, { label: "Last 30 days", value: "30" }, { label: "Last 90 days", value: "90" }, { label: "Last 365 days", value: "365" }, ]; for (const r of ranges) rangeSelect.createEl("option", { text: r.label, value: r.value }); const tableContainer = dv.el("div", ""); // ===== Helpers ===== const dateISO = (p) => { const d = p.date; if (!d) return null; if (d.toISODate) return d.toISODate(); if (typeof d === "string") return d.slice(0, 10); return null; }; const localISO = (d) => `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`; const makeWikiLink = (file, label) => { const a = document.createElement("a"); a.className = "internal-link"; a.setAttribute("data-href", file.path); a.setAttribute("href", file.path); a.textContent = label || file.name; a.addEventListener("click", (e) => { e.preventDefault(); app.workspace.openLinkText(file.path, "", e.ctrlKey || e.metaKey); }); return a; }; // ===== Render ===== const render = () => { tableContainer.empty(); const typeFilter = typeSelect.value; const rangeFilter = rangeSelect.value; let items = [...all]; if (typeFilter !== "All") { items = items.filter(p => p.type === typeFilter); } if (rangeFilter !== "all") { const days = parseInt(rangeFilter, 10); const cutoff = new Date(); cutoff.setHours(0, 0, 0, 0); cutoff.setDate(cutoff.getDate() - days); const cutoffStr = localISO(cutoff); items = items.filter(p => { const d = dateISO(p); return d && d >= cutoffStr; }); } if (items.length === 0) { tableContainer.createEl("p", { text: "No activities match the current filters.", attr: { style: "color: var(--text-muted); font-style: italic; margin-top: 0.5em;" } }); return; } const table = tableContainer.createEl("table", { cls: "dataview" }); const thead = table.createEl("thead"); const hdr = thead.createEl("tr"); for (const h of ["Note", "Type", "Date", "Subject"]) hdr.createEl("th", { text: h }); const tbody = table.createEl("tbody"); for (const item of items) { const tr = tbody.createEl("tr"); const c1 = tr.createEl("td"); c1.appendChild(makeWikiLink(item.file)); tr.createEl("td", { text: item.type || "" }); tr.createEl("td", { text: String(item.date || "") }); const subject = item.subject || item.topic || item.file.name; tr.createEl("td", { text: String(subject) }); } }; typeSelect.addEventListener("change", render); rangeSelect.addEventListener("change", render); render(); ``` ## Roster check ```dataview LIST FROM "People/Students" WHERE contains(classes, this.class) SORT file.name ASC ```