1/** 2 * KeepService â framework-agnostic job "keep" (favorite) logic. 3 * Mirrors the Angular User.addFavorite/favoriteDelete + LocalKeepService + 4 * NotificationsService behaviour so Vue pages behave identically. 5 * 6 * window.KeepService.toggle(jobId, currentlyKept) -> Promise<boolean> (new state) 7 */ 8(function () { 9 'use strict'; 10 11 var AUTH_COOKIE_KEY = 'zosid'; 12 var LOCAL_KEEP_STORE_KEY = 'keepstore'; 13 14 function isLoggedIn() { 15 var checkKey = AUTH_COOKIE_KEY + '='; 16 var cookies = decodeURIComponent(document.cookie).split('; '); 17 return cookies.some(function (cookie) { 18 return cookie.indexOf(checkKey) === 0; 19 }); 20 } 21 22 // --- guest keep store (same shape as Angular LocalKeepService) --- 23 function readStore() { 24 var data = window.localStorage.getItem(LOCAL_KEEP_STORE_KEY); 25 return data ? JSON.parse(data) : []; 26 } 27 function writeStore(keeps) { 28 window.localStorage.setItem(LOCAL_KEEP_STORE_KEY, JSON.stringify(keeps)); 29 } 30 function localIds() { 31 return readStore().map(function (k) { return k.jobId; }); 32 } 33 function localAdd(jobId) { 34 var keeps = readStore(); 35 if (!keeps.some(function (k) { return k.jobId === jobId; })) { 36 var now = new Date(); 37 keeps.push({ 38 added_at: [now.getFullYear(), now.getMonth(), now.getDate(), now.getHours(), now.getMinutes(), now.getSeconds()], 39 jobId: jobId 40 }); 41 writeStore(keeps); 42 } 43 } 44 function localRemove(jobId) { 45 writeStore(readStore().filter(function (k) { return k.jobId !== jobId; })); 46 } 47 48 // --- header keep-count badge (ported from Angular NotificationsService) --- 49 function incrementHeaderCount(loggedIn) { 50 var keepCountPc = document.getElementById('keep-count-pc'); 51 var keepCountSpNav = document.getElementById('keep-link-sp-nav'); 52 if (keepCountPc == null) { 53 var href = loggedIn ? '/user/me/favorite/form' : '/keeps'; 54 var keepNotificationPc = '<a target="_self" href="' + href + '" id="keep-count-pc" class="i-btn i-btn--small i-btn-border--yellow i-btn-icon--star i-text-yellow">1</a>'; 55 var keepNotificationSp = '<a target="_self" href="' + href + '" id="keep-count-sp" class="i-btn i-btn--small i-btn-border--yellow i-btn-icon--star i-text-yellow">1</a>'; 56 var unreadCountPc = document.getElementById('unread-count-pc'); 57 if (unreadCountPc == null) { 58 var naviPc = document.getElementById('header-navi-pc'); 59 var naviSp = document.getElementById('header-navi-sp'); 60 if (naviPc) { naviPc.children[0].insertAdjacentHTML('afterend', keepNotificationPc); } 61 if (naviSp) { naviSp.insertAdjacentHTML('afterbegin', keepNotificationSp); } 62 } else { 63 unreadCountPc.insertAdjacentHTML('afterend', keepNotificationPc); 64 var unreadCountSp = document.getElementById('unread-count-sp'); 65 if (unreadCountSp) { unreadCountSp.insertAdjacentHTML('afterend', keepNotificationSp); } 66 } 67 if (keepCountSpNav) { 68 keepCountSpNav.children[1].insertAdjacentHTML('afterend', '<span id="keep-count-sp-nav" class="i-nav__link-alert i-save">1</span>'); 69 } 70 } else { 71 var next = parseInt(keepCountPc.innerText, 10) + 1; 72 keepCountPc.innerText = next.toString(); 73 var sp = document.getElementById('keep-count-sp'); 74 if (sp) { sp.innerText = next.toString(); } 75 var spNav = document.getElementById('keep-count-sp-nav'); 76 if (spNav) { spNav.innerText = next.toString(); } 77 } 78 } 79 function decrementHeaderCount() { 80 var keepCountPc = document.getElementById('keep-count-pc'); 81 var keepCountSp = document.getElementById('keep-count-sp'); 82 var keepCountSpNav = document.getElementById('keep-count-sp-nav'); 83 if (keepCountPc != null) { 84 var next = parseInt(keepCountPc.innerText, 10) - 1; 85 if (next === 0) { 86 keepCountPc.remove(); 87 if (keepCountSp) { keepCountSp.remove(); } 88 if (keepCountSpNav) { keepCountSpNav.remove(); } 89 } else { 90 keepCountPc.innerText = next.toString(); 91 if (keepCountSp) { keepCountSp.innerText = next.toString(); } 92 if (keepCountSpNav) { keepCountSpNav.innerText = next.toString(); } 93 } 94 } 95 } 96 97 // --- server calls (same endpoints as Angular User resource) --- 98 // POST /user/me/favorite reads jobId from the QUERY string (@QueryParam), matching 99 // the Angular $resource's `jobId:'@jobId'` param mapping â not the request body. 100 function addServer(jobId) { 101 return fetch('/user/me/favorite?jobId=' + encodeURIComponent(jobId), { 102 method: 'POST', 103 credentials: 'same-origin' 104 }).then(function (res) { 105 return res.json().catch(function () { return {}; }).then(function (body) { 106 return { ok: res.ok, body: body }; 107 }); 108 }); 109 } 110 function removeServer(jobId) { 111 return fetch('/user/me/favorite/delete/' + jobId, { 112 method: 'DELETE', 113 credentials: 'same-origin' 114 }).then(function (res) { 115 return res.json().catch(function () { return {}; }).then(function (body) { 116 return { ok: res.ok, body: body }; 117 }); 118 }); 119 } 120 121 function redirectIfUnauthenticated(body) { 122 if (body && body.code === 9) { 123 window.location.href = body.redirect || '/user/login/form'; 124 return true; 125 } 126 return false;
127 } 128 129 /** 130 * Toggle keep state for a job. Returns a Promise resolving to the new 131 * kept-state (true/false). On failure, rejects (caller keeps old state). 132 * `loggedIn` should be the server's authoritative login state; if omitted, 133 * falls back to the (less reliable) cookie check. 134 */ 135 function toggle(jobId, currentlyKept, loggedIn) { 136 if (loggedIn === undefined) { loggedIn = isLoggedIn(); } 137 if (loggedIn) { 138 if (!currentlyKept) { 139 return addServer(jobId).then(function (r) { 140 if (redirectIfUnauthenticated(r.body)) { return currentlyKept; } 141 if (!r.ok) { throw new Error('addFavorite failed'); } 142 incrementHeaderCount(true); 143 return true; 144 }); 145 } 146 return removeServer(jobId).then(function (r) { 147 if (redirectIfUnauthenticated(r.body)) { return currentlyKept; } 148 if (!r.ok) { throw new Error('favoriteDelete failed'); } 149 decrementHeaderCount(); 150 return false; 151 }); 152 } 153 // guest 154 if (!currentlyKept) { 155 localAdd(jobId); 156 incrementHeaderCount(false); 157 return Promise.resolve(true); 158 } 159 localRemove(jobId); 160 decrementHeaderCount(); 161 return Promise.resolve(false); 162 } 163 164 window.KeepService = { 165 isLoggedIn: isLoggedIn, 166 localIds: localIds, 167 toggle: toggle 168 }; 169})();
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.