PageSourceSearch

https://campus.gov.il/wp-content/themes/campusnew/inc/course-card/…/card-menu-mail.js?ver=1.0.0.1790082021

js campus.gov.il collected 2026-10-02 06:35:23 UTC 7,189 bytes, 159 lines download raw bytes

1/* The card menu's mail level: whether a course the reader is taking sends them its updates.
2 *
3 * The answer lives in the LMS, not here. When an enrolled card's menu opens, the learner's dashboard
4 * data is read once for the page -- api/learner_home/init, the same call the LMS's own dashboard makes
5 * -- and the level's radios are set from it: `hasOptedOutOfEmail` chooses the radio, and a course whose
6 * `isEmailEnabled` is false loses the row, because it sends no mail to choose about. Until that answer
7 * arrives the radios stay disabled; if it never does, the level says so instead of guessing.
8 *
9 * Choosing posts to api/change_email_settings the way the LMS dashboard does: `receive_emails=on` to
10 * receive, its absence to stop. Form-encoded, the marketing CSRF cookie as the token, the learner's
11 * cookies sent with it -- the same shape as the enrol and unenrol calls, so no preflight.
12 */
13(function () {
14    'use strict';
15
16    var dashboards = {};  // LMS address -> promise of { courseId: enrollment }
17    var live = null;
18
19    function lmsBase(lms) { return lms.replace(/\/?$/, '/'); }
20
21    function dashboard(lms) {
22        if (!dashboards[lms]) {
23            dashboards[lms] = fetch(lmsBase(lms) + 'api/learner_home/init/', { credentials: 'include' })
24                .then(function (response) {
25                    if (!response.ok) throw new Error(String(response.status));
26                    return response.json();
27                })
28                .then(function (data) {
29                    var byCourse = {};
30                    (data.courses || []).forEach(function (course) {
31                        if (course.courseRun && course.enrollment) byCourse[course.courseRun.courseId] = course.enrollment;
32                    });
33                    return byCourse;
34                });
35            // A read that failed can be asked for again the next time a menu opens.
36            dashboards[lms].catch(function () { delete dashboards[lms]; });
37        }
38        return dashboards[lms];
39    }
40
41    function cookie(name) {
42        var pairs = document.cookie ? document.cookie.split('; ') : [];
43        for (var i = 0; i < pairs.length; i++) {
44            var at = pairs[i].indexOf('=');
45            if (pairs[i].slice(0, at) === name) return decodeURIComponent(pairs[i].slice(at + 1));
46        }
47        return '';
48    }
49
50    // The same polite region card-menu.js speaks through, whichever file needs it first.
51    function say(text) {
52        if (!text) return;
53        live = live || document.querySelector('.cil-card__live');
54        if (!live) {
55            live = document.createElement('div');
56            live.className = 'cil-card__live';
57            live.setAttribute('role', 'status');
58            live.setAttribute('aria-live', 'polite');
59            document.body.appendChild(live);
60        }
61        live.textContent = '';
62        window.setTimeout(function () { live.textContent = text; }, 50);
63    }
64
65    function radios(group) { return group.querySelectorAll('.cil-cardMenu__radio'); }
66
67    function busy(group, isBusy) {
68        group.setAttribute('aria-busy', String(isBusy));
69        radios(group).forEach(function (radio) { radio.disabled = isBusy; });
70    }
71
72    function choose(group, value) {
73        group.setAttribute('data-current', value);
74        radios(group).forEach(function (radio) { radio.checked = radio.value === value; });
75    }
76
77    /** Set the level from what the LMS said, or say it could not be read. */
78    function apply(panel, group, enrollment) {
79        var note = group.querySelector('.cil-cardMenu__mailNote');
80
81        if (enrollment && enrollment.isEmailEnabled === false) {
82            panel.querySelectorAll('[data-card-mail-row]').forEach(function (el) { el.hidden = true; });
83            return;
84        }
85        if (!enrollment) {
86            if (note) { note.textContent = group.getAttribute('data-unavailable') || ''; note.hidden = false; }
87            group.setAttribute('aria-busy', 'false');
88            return;
89        }
90        if (note) note.hidden = true;
91        choose(group, enrollment.hasOptedOutOfEmail ? 'off' : 'on');
92        busy(group, false);
93    }
94
95    function prepare(panel) {
96        var group = panel.querySelector('[data-card-mail]');
97        var lms = panel.getAttribute('data-card-menu-lms') || '';
98        if (!group || !lms || group.getAttribute('data-state') === 'loading' || group.getAttribute('data-state') === 'ready') return;
99
100        group.setAttribute('data-state', 'loading');
101        dashboard(lms).then(function (byCourse) {
102            group.setAttribute('data-state', 'ready');
103            apply(panel, group, byCourse[group.getAttribute('data-course-id')] || null);
104        }, function () {
105            group.removeAttribute('data-state');
106            apply(panel, group, null);
107        });
108    }
109
110    function save(radio) {
111        var group = radio.closest('[data-card-mail]');
112        var panel = radio.closest('[data-card-menu-panel]');
113        var lms = panel ? panel.getAttribute('data-card-menu-lms') || '' : '';
114        var course = group.getAttribute('data-course-id') || '';
115        var was = group.getAttribute('data-current') || '';
116        if (!lms || !course || radio.value === was) return;
117
118        var body = new URLSearchParams();
119        body.set('course_id', course);
120        body.set('csrfmiddlewaretoken', cookie('csrftoken-marketing'));
121        if (radio.value === 'on') body.set('receive_emails', 'on');
122
123        busy(group, true);
124        fetch(lmsBase(lms) + 'api/change_email_settings', {
125            method: 'POST',
126            credentials: 'include',
127            headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' },
128            body: body.toString()
129        }).then(function (response) {
130            if (!response.ok) throw new Error(String(response.status));
131            choose(group, radio.value);
132            // Kept in step with what was just saved, so another card for this course opens right.
133            dashboard(lms).then(function (byCourse) {
134                if (byCourse[course]) byCourse[course].hasOptedOutOfEmail = radio.value === 'off';
135            }, function () {});
136            busy(group, false);
137            say(group.getAttribute('data-saved'));
138        }).catch(function () {
139            choose(group, was);
140            busy(group, false);
141            say(group.getAttribute('data-failed'));
142        });
143    }
144
145    // Read as the menu opens rather than when the level does: by the time the reader reaches the row
146    // the answer is usually there, and a course that sends no mail has already lost the row.
147    document.addEventListener('click', function (event) {
148        var button = event.target.closest('[data-card-menu]');
149        if (!button) return;
150        var card = button.closest('.cil-card');
151        var panel = card ? card.querySelector('[data-card-menu-panel][data-card-menu-lms]') : null;
152        if (panel) prepare(panel);
153    });
154
155    document.addEventListener('change', function (event) {
156        var radio = event.target.closest ? event.target.closest('[data-card-mail] .cil-cardMenu__radio') : null;
157        if (radio && radio.checked) save(radio);
158    });
159})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.