1import { html } from "../../_npm/[email protected]/11521f02.js"; 2import markdownit from "../../_npm/[email protected]/173851b8.js"; 3import { getPersonalCase } from "../data/queries.a9520730.js" 4import { declOfNum, removeColonUrlParams, getVestochkaUrl } from "./helpers.de78684b.js" 5 6const Markdown = new markdownit({html: true}); 7 8function md(strings) { 9 let string = strings[0]; 10 for (let i = 1; i < arguments.length; ++i) { 11 string += String(arguments[i]); 12 string += strings[i]; 13 } 14 const template = document.createElement("template"); 15 template.innerHTML = Markdown.render(string); 16 return template.content.cloneNode(true); 17} 18 19const STATE_CONFIG = { 20 'At large': { cls: 'green', labelRu: 'Ðа Ñвободе', labelEn: 'At large' }, 21 'Under investigation': { cls: 'orange', labelRu: 'Ðод ÑледÑÑвием', labelEn: 'Under investigation' }, 22 'Serving a sentence': { cls: 'red', labelRu: 'ÐÑбÑÐ²Ð°ÐµÑ Ð½Ð°ÐºÐ°Ð·Ð°Ð½Ð¸Ðµ', labelEn: 'Serving a sentence' }, 23 'Imprisoned': { cls: 'red', labelRu: 'РзаклÑÑении', labelEn: 'Imprisoned' }, 24 'Not in Russia': { cls: 'gray', labelRu: 'Ðе в РоÑÑии', labelEn: 'Not in Russia' }, 25} 26const getStateConfig = state => STATE_CONFIG[state] ?? { cls: 'gray', labelRu: 'ÐеизвеÑÑно', labelEn: 'Unknown' } 27 28const STATE_COLOR = { green: '#10b981', orange: '#f97316', red: '#ef4444', gray: '#9ca3af' } 29 30function renderTimeline(item, lang) { 31 const startYear = item.started_at ? new Date(item.started_at).getUTCFullYear() : null 32 const verdictYear = item.verdict_date ? new Date(item.verdict_date).getUTCFullYear() : null 33 const endedYear = item.ended_at ? new Date(item.ended_at).getUTCFullYear() : null 34 const now = new Date().getUTCFullYear() 35 const isActual = !!item.actual 36 37 if (!startYear) return null 38 39 const L = { 40 start: lang === 'ru' ? 'ÐаÑало' : 'Start', 41 verdict: lang === 'ru' ? 'ÐÑиговоÑ' : 'Verdict', 42 end: lang === 'ru' ? 'ÐонеÑ' : 'End', 43 now: lang === 'ru' ? 'ÑейÑаÑ' : 'now', 44 } 45 46 const yrsLabel = n => `${n}\u00a0${lang === 'ru' ? declOfNum(n, ['год', 'года', 'леÑ']) : (n === 1 ? 'yr' : 'yrs')}` 47 48 const finalYear = isActual ? now : (endedYear ?? verdictYear ?? now) 49 const phase1End = verdictYear ?? finalYear 50 const p1 = Math.max(phase1End - startYear, 1) 51 const p2 = (verdictYear && isActual) ? Math.max(now - verdictYear, 1) : 0 52 const total = p1 + p2 || 1 53 54 const pct1 = `${(p1 / total * 100).toFixed(1)}%` 55 const pct2 = `${(p2 / total * 100).toFixed(1)}%` 56 57 const stateCfg = getStateConfig(item.person_state_en ?? item.state_en) 58 const stateColor = STATE_COLOR[stateCfg.cls] ?? '#9ca3af' 59 60 const p1Yrs = phase1End - startYear 61 const p2Yrs = now - (verdictYear ?? now) 62 const throughLabel = p1Yrs > 0 63 ? (lang === 'ru' ? `ЧеÑез\u00a0${yrsLabel(p1Yrs)}` : `After\u00a0${yrsLabel(p1Yrs)}`) 64 : null 65 66 const tl = document.createElement('div') 67 tl.className = 'timeline' 68 69 tl.innerHTML = ` 70 <div class="tl-bar" style="padding-right:12px"> 71 <span class="tl-dot"></span> 72 <span class="tl-line phase1" style="width:${pct1}"></span> 73 ${verdictYear ? `<span class="tl-dot verdict"></span>` : ''} 74 ${p2 >
74 0 ? `<span class="tl-line phase2" style="width:${pct2};--sc:${stateColor}"></span>` : ''} 75 <span class="tl-dot end${isActual ? ' active' : ''}" ${isActual ? `style="--sc:${stateColor}"` : ''}></span> 76 </div> 77 ` 78 79 const labels = document.createElement('div') 80 labels.className = 'tl-labels' 81 82 const startSpan = document.createElement('span') 83 startSpan.className = 'tl-start' 84 startSpan.innerHTML = `<b>${startYear}</b><span>${L.start}</span>` 85 86 const midSpan = verdictYear 87 ? html`<span class="tl-mid"> 88 <b>${L.verdict}: ${verdictYear}</b> 89 ${throughLabel ? html`<span class="tl-dur">${throughLabel}</span>` : ''} 90 </span>` 91 : html`<span class="tl-mid"></span>` 92 93 const endSpan = isActual 94 ? html`<span class="tl-end" style="color:${stateColor}"> 95 <b>${L.now}</b> 96 ${p2Yrs > 0 ? html`<span class="tl-dur">${lang === 'ru' ? `СпÑÑÑÑ\u00a0${yrsLabel(p2Yrs)}` : `After\u00a0${yrsLabel(p2Yrs)}`}</span>` : ''} 97 </span>` 98 : (endedYear 99 ? html`<span class="tl-end"><b>${endedYear}</b><span>${L.end}</span></span>` 100 : html`<span class="tl-end"></span>`) 101 102 labels.appendChild(startSpan) 103 labels.appendChild(midSpan) 104 labels.appendChild(endSpan) 105 tl.appendChild(labels) 106 107 return tl 108} 109
110function renderSentence(item, lang) { 111 if (!item.verdict_essence) return null 112 113 const prefix = lang === 'ru' ? 'ÐÑиговоÑ:\u00a0' : 'Sentence:\u00a0' 114 const el = document.createElement('div') 115 el.className = 'drawer-sentence' 116 el.innerHTML = `<b class="sentence-prefix">${prefix}</b>${item.verdict_essence}` 117 return el 118} 119 120function getDrawer() { 121 const existing = document.getElementById("_profile-drawer"); 122 if (existing) return existing; 123 124 const overlay = document.createElement("div"); 125 overlay.id = "_profile-overlay"; 126 127 const drawer = document.createElement("div"); 128 drawer.id = "_profile-drawer"; 129 130 const closeBtn = document.createElement("button"); 131 closeBtn.id = "_profile-close"; 132 closeBtn.textContent = "â"; 133 closeBtn.title = "ÐакÑÑÑÑ"; 134 135 const content = document.createElement("div"); 136 content.id = "_profile-content"; 137 138 const letterFooter = document.createElement("div"); 139 letterFooter.id = "_profile-letter-footer"; 140 letterFooter.style.display = "none"; 141 142 const footerCloseBtn = document.createElement("button"); 143 footerCloseBtn.id = "_profile-footer-close"; 144 footerCloseBtn.title = "ÐакÑÑÑÑ"; 145 footerCloseBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg><span>ÐакÑÑÑÑ</span>`; 146 letterFooter.appendChild(footerCloseBtn); 147 148 drawer.appendChild(closeBtn); 149 drawer.appendChild(content); 150 drawer.appendChild(letterFooter); 151 152 const helpBtn = () => document.querySelector("._oi_help_btn"); 153 const close = () => { 154 overlay.classList.remove("open"); 155 drawer.classList.remove("open"); 156 removeColonUrlParams(); 157 const btn = helpBtn(); if (btn) btn.style.visibility = ""; 158 }; 159 160 overlay.addEventListener("click", close); 161 closeBtn.addEventListener("click", close); 162 footerCloseBtn.addEventListener("click", close); 163 document.addEventListener("keydown", e => { if (e.key === "Escape") close(); }); 164 165 document.body.appendChild(overlay); 166 document.body.appendChild(drawer); 167 168 return drawer; 169} 170 171export const profileInspectorSql = async (personId, options) => { 172 const { lang, urlParam } = options; 173 const drawer = getDrawer(); 174 const overlay = document.getElementById("_profile-overlay"); 175 const content = document.getElementById("_profile-content"); 176 const letterFooter = document.getElementById("_profile-letter-footer"); 177 178 const closeLabel = lang === 'en' ? 'Close' : 'ÐакÑÑÑÑ'; 179 const closeBtn = document.getElementById("_profile-close"); 180 if (closeBtn) closeBtn.title = closeLabel; 181 const footerCloseBtn = document.getElementById("_profile-footer-close"); 182 if (footerCloseBtn) { 183 footerCloseBtn.title = closeLabel; 184 const span = footerCloseBtn.querySelector('span'); 185 if (span) span.textContent = closeLabel; 186 } 187 188 if (personId === null) { 189 drawer.classList.remove("open"); 190 overlay.classList.remove("open"); 191 letterFooter.style.display = "none"; 192 return document.createElement("div"); 193 } 194 195 const data = await getPersonalCase(personId); 196 if (!data) return document.createElement("div"); 197 content.innerHTML = ""; 198 199 // Photo 200 if (data.photo) { 201 const img = document.createElement("img"); 202 img.className = "drawer-photo"; 203 img.src = data.photo; 204 content.appendChild(img); 205 } 206 207 // Name, age/region, status 208 const fragments = []; 209 if (data.birth_date) { 210 const age = Math.abs(new Date(Date.now() - new Date(data.birth_date)).getUTCFullYear() - 1970); 211 fragments.push(`${age}\u00a0${lang === 'ru' ? declOfNum(age, ["год", "года", "леÑ"]) : 'y.o.'}`); 212 } 213 if (data.region) { 214 const region = data.region?.toJSON ? data.region.toJSON() : String(data.region); 215 fragments.push(region); 216 } 217 218 const stateCfg = getStateConfig(data.person_state_en ?? data.state_en); 219 const stateLabel = lang === 'ru' ? stateCfg.labelRu : stateCfg.labelEn; 220 221 content.appendChild(html`<div class="drawer-info person-info">
222 <p class="drawer-name">${data.name}</p> 223 ${fragments.length ? html`<p class="drawer-meta">${fragments.join(' · ')}</p>` : ''} 224 <p class="location"><span class="state ${stateCfg.cls}"></span>${stateLabel}</p> 225 ${(data.penal_facility || data.detention_center) ? html`<p class="facility">${data.penal_facility || data.detention_center}</p>` : ''} 226 </div>`); 227 228 // Timeline 229 const timeline = renderTimeline(data, lang); 230 if (timeline) content.appendChild(timeline); 231 232 // Sentence 233 const sentence = renderSentence(data, lang); 234 if (sentence) content.appendChild(sentence); 235 236 // Story 237 if (data.story) { 238 const storyEl = document.createElement("div"); 239 storyEl.className = "drawer-story"; 240 storyEl.appendChild(md`${data.story}`); 241 content.appendChild(storyEl); 242 } 243 244 // Letter footer 245 letterFooter.querySelector(".drawer-letter-btn")?.remove(); 246 const footerClose = document.getElementById("_profile-footer-close"); 247 if (data.letters && data.slug) { 248 const btnLabel = lang === 'ru' ? 'ÐапиÑаÑÑ Ð¿Ð¸ÑÑмо' : 'Write a letter'; 249 const letterBtn = html`<a href="${getVestochkaUrl(data.slug, 'en')}" target="_blank" rel="noopener noreferrer" class="drawer-letter-btn"> 250 <svg xmlns="http://www.w3.org/2000/svg" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="2" y="4" width="20" height="16" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg> 251 ${btnLabel} 252 </a>`; 253 letterFooter.insertBefore(letterBtn, footerClose); 254 } 255 letterFooter.style.display = ""; 256 257 overlay.classList.add("open"); 258 drawer.classList.add("open"); 259 const helpBtnEl = document.querySelector("._oi_help_btn"); if (helpBtnEl) helpBtnEl.style.visibility = "hidden"; 260 261 if (urlParam && data.person_id) { 262 const [base, search] = window.location.href.split('?') 263 const parts = (search || '').split('&').filter(p => p && !p.startsWith(`${urlParam}:`)) 264 parts.push(`${urlParam}:${data.person_id}`) 265 history.pushState({}, "", `${base}?${parts.join('&')}`) 266 } 267 268 return document.createElement("div"); 269}
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.