Repository navigation
Expand file tree
/
Copy pathcourse-progress.js
More file actions
74 lines (69 loc) · 2.74 KB
/
Copy pathcourse-progress.js
File metadata and controls
74 lines (69 loc) · 2.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
// Reading progress kept in this browser only (localStorage, no account):
// new (not opened), seen (opened), done (marked as read by the reader).
// Progress is per article and shared by its English and Russian pages.
(() => {
const storageKey = 'course-progress';
const labels = {
en: { new: 'Not opened', seen: 'Opened', done: 'Read' },
ru: { new: 'Не открыта', seen: 'Открыта', done: 'Прочитана' },
};
const language = document.documentElement.lang?.startsWith('ru') ? 'ru' : 'en';
function load() {
try { return JSON.parse(localStorage.getItem(storageKey) ?? '{}') || {}; } catch { return {}; }
}
function save(progress) {
try { localStorage.setItem(storageKey, JSON.stringify(progress)); } catch { /* storage may be off */ }
}
// "/ru/course/fundamentals/x/" and "/en/course/fundamentals/x/" share "/course/fundamentals/x/".
function articleKey(href) {
try {
const { pathname } = new URL(href, location.href);
const match = pathname.match(/^\/(?:en|ru)(\/course\/.+)$/);
return match ? (match[1].endsWith('/') ? match[1] : `${match[1]}/`) : null;
} catch { return null; }
}
const progress = load();
const currentKey = articleKey(location.href);
if (currentKey && !progress[currentKey]) {
progress[currentKey] = 'seen';
save(progress);
}
const stateOf = (key) => progress[key] ?? 'new';
function decorateSidebar() {
document.querySelectorAll('.sidebar-content ul a[href]').forEach((link) => {
const key = articleKey(link.href);
if (!key) return;
let mark = link.querySelector('.course-progress');
if (!mark) {
mark = document.createElement('span');
mark.className = 'course-progress';
link.prepend(mark);
}
const state = stateOf(key);
mark.dataset.state = state;
mark.title = labels[language][state];
mark.setAttribute('aria-label', labels[language][state]);
mark.setAttribute('role', 'img');
});
}
function setupToggle() {
const toggle = document.querySelector('[data-course-progress-toggle]');
if (!toggle || !currentKey) return;
const render = () => {
const done = stateOf(currentKey) === 'done';
toggle.setAttribute('aria-checked', String(done));
toggle.querySelector('.course-progress').dataset.state = done ? 'done' : 'seen';
};
toggle.addEventListener('click', () => {
progress[currentKey] = stateOf(currentKey) === 'done' ? 'seen' : 'done';
save(progress);
render();
decorateSidebar();
});
toggle.hidden = false;
render();
}
const start = () => { decorateSidebar(); setupToggle(); };
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start);
else start();
})();