Skip to content

Commit bfd4fcf

Browse files
author
committed
Deployed 5e51d0e with MkDocs version: 1.6.1
0 parents  commit bfd4fcf

223 files changed

Lines changed: 168283 additions & 0 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.nojekyll

Whitespace-only changes.

404.html

Lines changed: 1839 additions & 0 deletions
Large diffs are not rendered by default.

admin.html

Lines changed: 236 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,236 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<title>Quiz Progress — Teacher Dashboard</title>
6+
<style>
7+
body { font-family: sans-serif; max-width: 1100px; margin: 2rem auto; padding: 0 1rem; }
8+
table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
9+
th, td { border: 1px solid #ddd; padding: 8px 12px; text-align: left; }
10+
th { background: #f4f4f4; }
11+
tr:nth-child(even) { background: #fafafa; }
12+
select, input { padding: 6px; margin: 0 8px 1rem 0; }
13+
#auth-bar { margin-bottom: 1.5rem; }
14+
#message { color: #666; font-style: italic; }
15+
</style>
16+
</head>
17+
<body>
18+
<h1>Quiz Progress Dashboard</h1>
19+
20+
<div id="auth-bar">
21+
<button id="sign-in-btn" onclick="signIn()" style="display:none">Sign in with GitHub</button>
22+
<span id="user-label"></span>
23+
</div>
24+
25+
<div id="controls" style="display:none">
26+
<label>Filter by student:
27+
<input id="filter-student" type="text" placeholder="github login">
28+
</label>
29+
<label>Filter by quiz:
30+
<input id="filter-quiz" type="text" placeholder="quiz id">
31+
</label>
32+
<button onclick="loadResults()">Refresh</button>
33+
</div>
34+
35+
<p id="message"></p>
36+
37+
<table id="results-table" style="display:none">
38+
<thead>
39+
<tr>
40+
<th>Student</th>
41+
<th>GitHub Login</th>
42+
<th>Quiz ID</th>
43+
<th>Page</th>
44+
<th>Score</th>
45+
<th>Pct</th>
46+
<th>Submitted</th>
47+
</tr>
48+
</thead>
49+
<tbody id="results-body"></tbody>
50+
</table>
51+
52+
<script>
53+
const SUPABASE_URL = 'https://wltmawdleuvcjxqtzkmj.supabase.co';
54+
const SUPABASE_ANON_KEY = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6IndsdG1hd2RsZXV2Y2p4cXR6a21qIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NzY4MDI5MDEsImV4cCI6MjA5MjM3ODkwMX0.MGZavTM0z6n1RjOJaxR1jj1Emr0zsKCPt38L4k_CO5U';
55+
56+
const projectId = new URL(SUPABASE_URL).hostname.split('.')[0];
57+
const SESSION_KEY = `sb-${projectId}-auth-token`;
58+
59+
function handleOAuthCallback() {
60+
const hash = window.location.hash;
61+
if (!hash.includes('access_token=')) return;
62+
63+
const params = new URLSearchParams(hash.slice(1));
64+
const accessToken = params.get('access_token');
65+
const refreshToken = params.get('refresh_token');
66+
const expiresIn = parseInt(params.get('expires_in') || '3600', 10);
67+
68+
if (!accessToken) return;
69+
70+
fetch(`${SUPABASE_URL}/auth/v1/user`, {
71+
headers: {
72+
'apikey': SUPABASE_ANON_KEY,
73+
'Authorization': `Bearer ${accessToken}`,
74+
},
75+
})
76+
.then(r => r.json())
77+
.then(user => {
78+
const session = {
79+
access_token: accessToken,
80+
refresh_token: refreshToken,
81+
expires_at: Math.floor(Date.now() / 1000) + expiresIn,
82+
user,
83+
};
84+
localStorage.setItem(SESSION_KEY, JSON.stringify(session));
85+
history.replaceState(null, '', window.location.pathname + window.location.search);
86+
init(); // re-run now that session is stored
87+
});
88+
}
89+
90+
function getSession() {
91+
try {
92+
return JSON.parse(localStorage.getItem(SESSION_KEY));
93+
} catch (_) { return null; }
94+
}
95+
96+
function isSessionValid(session) {
97+
if (!session?.access_token) return false;
98+
if (!session.expires_at) return false;
99+
const nowInSeconds = Math.floor(Date.now() / 1000);
100+
return session.expires_at > nowInSeconds;
101+
}
102+
103+
async function refreshSession(session) {
104+
if (!session?.refresh_token) return null;
105+
106+
try {
107+
const res = await fetch(`${SUPABASE_URL}/auth/v1/token?grant_type=refresh_token`, {
108+
method: 'POST',
109+
headers: {
110+
'Content-Type': 'application/json',
111+
'apikey': SUPABASE_ANON_KEY,
112+
},
113+
body: JSON.stringify({
114+
refresh_token: session.refresh_token,
115+
}),
116+
});
117+
118+
if (!res.ok) return null;
119+
120+
const newSession = await res.json();
121+
const updated = {
122+
access_token: newSession.access_token,
123+
refresh_token: newSession.refresh_token || session.refresh_token,
124+
expires_at: Math.floor(Date.now() / 1000) + (newSession.expires_in || 3600),
125+
user: session.user,
126+
};
127+
localStorage.setItem(SESSION_KEY, JSON.stringify(updated));
128+
return updated;
129+
} catch (_) {
130+
return null;
131+
}
132+
}
133+
134+
function signIn() {
135+
const redirectTo = window.location.origin + window.location.pathname + window.location.search;
136+
const url = `${SUPABASE_URL}/auth/v1/authorize?provider=github&redirect_to=${encodeURIComponent(redirectTo)}`;
137+
window.location.href = url;
138+
}
139+
140+
function signOut() {
141+
localStorage.removeItem(SESSION_KEY);
142+
init();
143+
}
144+
145+
async function loadResults() {
146+
const session = getSession();
147+
if (!session?.access_token) return;
148+
149+
const student = document.getElementById('filter-student').value.trim();
150+
const quiz = document.getElementById('filter-quiz').value.trim();
151+
152+
let url = `${SUPABASE_URL}/rest/v1/quiz_results?order=submitted_at.desc&limit=500`;
153+
if (student) url += `&github_login=eq.${encodeURIComponent(student)}`;
154+
if (quiz) url += `&quiz_id=eq.${encodeURIComponent(quiz)}`;
155+
156+
const res = await fetch(url, {
157+
headers: {
158+
'apikey': SUPABASE_ANON_KEY,
159+
'Authorization': `Bearer ${session.access_token}`,
160+
}
161+
});
162+
163+
// If token expired during the API call, handle it
164+
if (res.status === 401) {
165+
localStorage.removeItem(SESSION_KEY);
166+
document.getElementById('message').textContent = 'Session expired. Please sign in again.';
167+
document.getElementById('sign-in-btn').style.display = '';
168+
document.getElementById('user-label').innerHTML = '<button onclick="signIn()" style="padding:4px 8px;cursor:pointer;">Sign in</button>';
169+
document.getElementById('controls').style.display = 'none';
170+
document.getElementById('results-table').style.display = 'none';
171+
return;
172+
}
173+
174+
const rows = await res.json();
175+
const msg = document.getElementById('message');
176+
177+
if (!Array.isArray(rows) || rows.length === 0) {
178+
msg.textContent = rows.length === 0
179+
? 'No results found.'
180+
: 'No results returned — your GitHub account may not be in the teachers table.';
181+
document.getElementById('results-table').style.display = 'none';
182+
return;
183+
}
184+
185+
msg.textContent = '';
186+
const table = document.getElementById('results-table');
187+
table.style.display = '';
188+
document.getElementById('results-body').innerHTML = rows.map(r => `
189+
<tr>
190+
<td>${r.student_name}</td>
191+
<td><a href="https://github.com/${r.github_login}" target="_blank">${r.github_login}</a></td>
192+
<td>${r.quiz_id}</td>
193+
<td>${r.page_url}</td>
194+
<td>${r.score} / ${r.total}</td>
195+
<td>${r.pct}%</td>
196+
<td>${new Date(r.submitted_at).toLocaleString()}</td>
197+
</tr>
198+
`).join('');
199+
}
200+
201+
async function init() {
202+
let session = getSession();
203+
const msg = document.getElementById('message');
204+
205+
// Check if session exists and is valid
206+
if (session && !isSessionValid(session)) {
207+
// Try to refresh the token
208+
session = await refreshSession(session);
209+
if (!session) {
210+
localStorage.removeItem(SESSION_KEY);
211+
}
212+
}
213+
214+
if (!session?.access_token) {
215+
document.getElementById('sign-in-btn').style.display = '';
216+
document.getElementById('user-label').innerHTML = '<button onclick="signIn()" style="padding:4px 8px;cursor:pointer;">Sign in</button>';
217+
msg.textContent = 'Sign in with your GitHub account to view results.';
218+
document.getElementById('controls').style.display = 'none';
219+
document.getElementById('results-table').style.display = 'none';
220+
return;
221+
}
222+
223+
const login = session.user?.user_metadata?.user_name || session.user?.email;
224+
document.getElementById('user-label').innerHTML = `Signed in as <strong>${login}</strong> <button onclick="signOut()" style="padding:4px 8px;margin-left:1rem;cursor:pointer;">Sign out</button>`;
225+
document.getElementById('controls').style.display = '';
226+
await loadResults();
227+
}
228+
229+
if (window.location.hash.includes('access_token=')) {
230+
handleOAuthCallback(); // calls init() when the session is stored
231+
} else {
232+
init();
233+
}
234+
</script>
235+
</body>
236+
</html>

assets/css/codehilite.css

Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
/////////////////
3+
// Inline Code //
4+
/////////////////
5+
*/
6+
7+
.md-typeset code {
8+
background-color: #424242;
9+
color: #F5F5F5;
10+
margin: 0;
11+
padding: 0.07353em 0.29412em;
12+
box-shadow: none;
13+
}
14+
15+
/*
16+
/////////////////
17+
// Code Blocks //
18+
/////////////////
19+
*/
20+
21+
/*
22+
line number
23+
*/
24+
.linenos {
25+
color: #F5F5F5 !important;
26+
background-color: #313131 !important;
27+
}
28+
29+
/*
30+
code block background
31+
*/
32+
.codehilite {
33+
background-color: #424242 !important;
34+
}
35+
36+
/*
37+
scroll bar size
38+
*/
39+
40+
.md-typeset .codehilite::-webkit-scrollbar {
41+
height: 1rem !important;
42+
}
43+
44+
/*
45+
actual syntax highlighting
46+
*/
47+
.codehilite pre { color: #FAFAFA !important; background-color: transparent !important; }
48+
.codehilite .hll { background-color: #272822 !important; }
49+
.codehilite .c { color: #a1a1b6 !important } /* Comment */
50+
.codehilite .err { color: #960050 !important; background-color: #1e0010 !important } /* Error */
51+
.codehilite .k { color: #66d9ef !important } /* Keyword */
52+
.codehilite .l { color: #ae81ff !important } /* Literal */
53+
.codehilite .n { color: #f8f8f2 !important } /* Name */
54+
.codehilite .o { color: #f92672 !important } /* Operator */
55+
.codehilite .p { color: #f8f8f2 !important } /* Punctuation */
56+
.codehilite .cm { color: #a1a1b6 !important } /* Comment.Multiline */
57+
.codehilite .cp { color: #a1a1b6 !important } /* Comment.Preproc */
58+
.codehilite .c1 { color: #a1a1b6 !important } /* Comment.Single */
59+
.codehilite .cs { color: #a1a1b6 !important } /* Comment.Special */
60+
.codehilite .ge { font-style: italic !important } /* Generic.Emph */
61+
.codehilite .gs { font-weight: bold !important } /* Generic.Strong */
62+
.codehilite .kc { color: #66d9ef !important } /* Keyword.Constant */
63+
.codehilite .kd { color: #66d9ef !important } /* Keyword.Declaration */
64+
.codehilite .kn { color: #f92672 !important } /* Keyword.Namespace */
65+
.codehilite .kp { color: #66d9ef !important } /* Keyword.Pseudo */
66+
.codehilite .kr { color: #66d9ef !important } /* Keyword.Reserved */
67+
.codehilite .kt { color: #66d9ef !important } /* Keyword.Type */
68+
.codehilite .ld { color: #e6db74 !important } /* Literal.Date */
69+
.codehilite .m { color: #ae81ff !important } /* Literal.Number */
70+
.codehilite .s { color: #e6db74 !important } /* Literal.String */
71+
.codehilite .na { color: #a6e22e !important } /* Name.Attribute */
72+
.codehilite .nb { color: #f8f8f2 !important } /* Name.Builtin */
73+
.codehilite .nc { color: #a6e22e !important } /* Name.Class */
74+
.codehilite .no { color: #66d9ef !important } /* Name.Constant */
75+
.codehilite .nd { color: #a6e22e !important } /* Name.Decorator */
76+
.codehilite .ni { color: #f8f8f2 !important } /* Name.Entity */
77+
.codehilite .ne { color: #a6e22e !important } /* Name.Exception */
78+
.codehilite .nf { color: #a6e22e !important } /* Name.Function */
79+
.codehilite .nl { color: #f8f8f2 !important } /* Name.Label */
80+
.codehilite .nn { color: #f8f8f2 !important } /* Name.Namespace */
81+
.codehilite .nx { color: #a6e22e !important } /* Name.Other */
82+
.codehilite .py { color: #f8f8f2 !important } /* Name.Property */
83+
.codehilite .nt { color: #f92672 !important } /* Name.Tag */
84+
.codehilite .nv { color: #f8f8f2 !important } /* Name.Variable */
85+
.codehilite .ow { color: #f92672 !important } /* Operator.Word */
86+
.codehilite .w { color: #f8f8f2 !important } /* Text.Whitespace */
87+
.codehilite .mf { color: #ae81ff !important } /* Literal.Number.Float */
88+
.codehilite .mh { color: #ae81ff !important } /* Literal.Number.Hex */
89+
.codehilite .mi { color: #ae81ff !important } /* Literal.Number.Integer */
90+
.codehilite .mo { color: #ae81ff !important } /* Literal.Number.Oct */
91+
.codehilite .sb { color: #e6db74 !important } /* Literal.String.Backtick */
92+
.codehilite .sc { color: #e6db74 !important } /* Literal.String.Char */
93+
.codehilite .sd { color: #e6db74 !important } /* Literal.String.Doc */
94+
.codehilite .s2 { color: #e6db74 !important } /* Literal.String.Double */
95+
.codehilite .se { color: #ae81ff !important } /* Literal.String.Escape */
96+
.codehilite .sh { color: #e6db74 !important } /* Literal.String.Heredoc */
97+
.codehilite .si { color: #e6db74 !important } /* Literal.String.Interpol */
98+
.codehilite .sx { color: #e6db74 !important } /* Literal.String.Other */
99+
.codehilite .sr { color: #e6db74 !important } /* Literal.String.Regex */
100+
.codehilite .s1 { color: #e6db74 !important } /* Literal.String.Single */
101+
.codehilite .ss { color: #e6db74 !important } /* Literal.String.Symbol */
102+
.codehilite .bp { color: #f8f8f2 !important } /* Name.Builtin.Pseudo */
103+
.codehilite .vc { color: #f8f8f2 !important } /* Name.Variable.Class */
104+
.codehilite .vg { color: #f8f8f2 !important } /* Name.Variable.Global */
105+
.codehilite .vi { color: #f8f8f2 !important } /* Name.Variable.Instance */
106+
.codehilite .il { color: #ae81ff !important } /* Literal.Number.Integer.Long */
107+
108+
.codehilite .gh { } /* Generic Heading & Diff Header */
109+
.codehilite .gu { color: #a1a1b6 !important ; } /* Generic.Subheading & Diff Unified/Comment? */
110+
.codehilite .gd { color: #f92672 !important ; } /* Generic.Deleted & Diff Deleted */
111+
.codehilite .gi { color: #a6e22e !important ; } /* Generic.Inserted & Diff Inserted */
112+
113+
.codehilite .md-clipboard:before { color: rgba(255, 255, 255, 0.07) } /* Clipboard button (no hover) */
114+
.codehilite:hover .md-clipboard:before { color: rgba(255, 255, 255, 0.54) } /* Clipboard button (hovered) */

0 commit comments

Comments
 (0)