1// Topluluklar ekranı â birden çok grubu tek çatı altında toplayan üst yapı. 2// 3// EKRANIN ANLATMASI GEREKEN ÅEY: topluluk grupları BİRLEÅTİRMEZ. Gruplar 4// olduÄu gibi kalır, üstlerine yalnız ortak bir "Duyurular" grubu eklenir. 5// Kullanıcı bunu bilmezse ya gruplarının kaybolacaÄını sanıp hiç kurmaz, ya 6// da tek bir dev grup beklediÄi için sonucu yanlıŠokur. 7// 8// Uçlar: 9// GET/POST /api/communities 10// GET/PATCH/DELETE /api/communities/{id} 11// POST /api/communities/{id}/groups { conversation_id } 12// DELETE /api/communities/{id}/groups/{cid} 13 14import { api, escapeHtml, toast } from './api-client.js'; 15import { t } from './i18n.js'; 16 17// `t()` karÅılıÄı OLMAYAN anahtar için ANAHTARIN KENDİSİNİ döndürür, bu 18// yüzden `t(k) || 'yedek'` kalıbı ÃALIÅMAZ (bkz. reminders-view.js). 19function T(anahtar, yedek) { 20 const v = t(anahtar); 21 return v === anahtar ? yedek : v; 22} 23 24let _host = null; 25let _topluluklar = []; 26let _acik = null; 27 28export async function renderTopluluklarView(host) { 29 _host = host; 30 host.innerHTML = `<div style="padding:28px;opacity:.7;">${ 31 escapeHtml(T('comm.loading', 'Yükleniyorâ¦'))}</div>`; 32 await _yenile(); 33} 34 35async function _yenile() { 36 try { 37 _topluluklar = (await api.get('/api/communities')).communities || []; 38 } catch (e) { 39 _host.innerHTML = `<div style="padding:28px;">${escapeHtml(e.message || '')}</div>`; 40 return; 41 } 42 if (_acik) { 43 try { _acik = await api.get(`/api/communities/${_acik.id}`); } 44 catch { _acik = null; } 45 } 46 _ciz(); 47} 48 49function _ciz() { 50 _host.innerHTML = ` 51 <div style="padding:22px 26px;max-width:1080px;"> 52 <div style="display:flex;align-items:center;gap:12px;margin-bottom:6px;"> 53 <h1 style="font-size:22px;font-weight:700;margin:0;">${ 54 escapeHtml(T('comm.title', 'Topluluklar'))}</h1> 55 <button id="tpYeni" class="btn btn-primary" type="button" style="margin-left:auto;"> 56 ${escapeHtml(T('comm.new', 'Yeni topluluk'))}</button> 57 </div> 58 <p style="margin:0 0 18px;font-size:13px;opacity:.75;max-width:640px;">${ 59 escapeHtml(T('comm.explain', 60 'Topluluk gruplarınızı birleÅtirmez. Gruplar olduÄu gibi kalır; ' 61 + 'üstlerine yalnız hepsine birden duyuru yapabileceÄiniz ortak bir ' 62 + '"Duyurular" grubu eklenir.'))}</p> 63 <div style="display:grid;grid-template-columns:280px 1fr;gap:18px;align-items:start;"> 64 <div id="tpListe"></div> 65 <div id="tpDetay"></div> 66 </div> 67 </div>`; 68 _host.querySelector('#tpYeni').addEventListener('click', _yeniTopluluk); 69 _listeyiCiz(); 70 _detayiCiz(); 71} 72 73function _listeyiCiz() { 74 const kap = _host.querySelector('#tpListe'); 75 if (!_topluluklar.length) { 76 kap.innerHTML = `<div style="padding:16px;border:1px dashed var(--border,#E6DEF0); 77 border-radius:12px;font-size:13px;opacity:.7;">${ 78 escapeHtml(T('comm.empty', 'Henüz topluluk yok.'))}</div>`; 79 return; 80 } 81 kap.innerHTML = _topluluklar.map(c => ` 82 <button type="button" class="tp-sat" data-id="${c.id}" 83 style="display:block;width:100%;text-align:left;border:1px solid var(--border,#E6DEF0); 84 background:${_acik && _acik.id === c.id ? 'var(--surface-2,#F3EEF8)' : 'transparent'}; 85 border-radius:10px;padding:10px 12px;margin-bottom:8px;
85cursor:pointer;font:inherit;"> 86 <span style="display:block;font-weight:600;font-size:13.5px;">${escapeHtml(c.name || '')}</span> 87 <span style="display:block;font-size:11.5px;opacity:.65;">${ 88 escapeHtml(T('comm.counts', '%g grup · %m kiÅi') 89 .replace('%g', c.group_count).replace('%m', c.member_count))}</span> 90 </button>`).join(''); 91 kap.querySelectorAll('.tp-sat').forEach(b => { 92 b.addEventListener('click', async () => { 93 try { _acik = await api.get(`/api/communities/${b.dataset.id}`); _ciz(); } 94 catch (e) { toast(e.message || ''); } 95 }); 96 }); 97} 98 99function _detayiCiz() { 100 const kap = _host.querySelector('#tpDetay'); 101 if (!_acik) { 102 kap.innerHTML = `<div style="padding:22px;font-size:13px;opacity:.7;">${ 103 escapeHtml(T('comm.pick', 'Soldan bir topluluk seçin.'))}</div>`; 104 return; 105 } 106 const c = _acik; 107 kap.innerHTML = ` 108 <div style="border:1px solid var(--border,#E6DEF0);border-radius:14px;padding:16px 18px;"> 109 <div style="display:flex;align-items:center;gap:10px;"> 110 <h2 style="margin:0;font-size:17px;font-weight:700;">${escapeHtml(c.name || '')}</h2> 111 ${c.is_admin ? ` 112 <button id="tpAdDegis" type="button" class="btn btn-ghost" style="margin-left:auto;"> 113 ${escapeHtml(T('comm.rename', 'Adı deÄiÅtir'))}</button> 114 <button id="tpSil" type="button" class="btn btn-ghost" style="color:var(--danger, #C33);"> 115 ${escapeHtml(T('comm.disband', 'DaÄıt'))}</button>` : ''} 116 </div> 117 ${c.description ? `<p style="margin:8px 0 0;font-size:13px;opacity:.8;">${ 118 escapeHtml(c.description)}</p>` : ''} 119 120 <div style="margin-top:14px;"> 121 <button id="tpDuyuru" type="button" class="btn btn-primary"> 122 ${escapeHtml(T('comm.open_announcements', 'Duyurular grubunu aç'))}</button> 123 <span style="font-size:11.5px;opacity:.7;margin-left:10px;">${ 124 escapeHtml(c.is_admin 125 ? T('comm.ann_admin', 'Buraya yalnız yöneticiler yazabilir.') 126 : T('comm.ann_member', 'Buraya yalnız yöneticiler yazar; siz okursunuz.'))}</span> 127 </div> 128 129 <div style="margin-top:18px;display:flex;align-items:center;gap:10px;"> 130 <h3 style="margin:0;font-size:14px;font-weight:600;">${ 131 escapeHtml(T('comm.groups', 'Gruplar'))}</h3> 132 <span style="font-size:11.5px;opacity:.65;">${ 133 escapeHtml(T('comm.counts', '%g grup · %m kiÅi') 134 .replace('%g', (c.groups || []).length).replace('%m', c.member_count))}</span> 135 ${c.is_admin ? `<button id="tpGrupEkle" type="button" class="btn btn-ghost" 136 style="margin-left:auto;">${escapeHtml(T('comm.add_group', 'Grup ekle'))}</button>` : ''} 137 </div> 138 <div id="tpGruplar" style="margin-top:8px;"></div> 139 ${c.is_admin ? `<p style="margin:10px 0 0;font-size:11.5px;opacity:.65;">${ 140 escapeHtml(T('comm.only_own_groups', 141 'Yalnız yöneticisi olduÄunuz gruplar eklenebilir.'))}</p>` : ''} 142 </div>`; 143 144 kap.querySelector('#tpDuyuru')?.addEventListener('click', () => { 145 // Duyuru grubu SIRADAN bir gruptur: sohbet ekranında açılıyor, ayrı bir 146 // yüzey icat etmiyoruz (okundu/bildirim/arama bedavaya geliyor). 147 location.hash = `#messages/${c.announcement_conversation_id}`; 148 }); 149 kap.querySelector('#tpAdDegis')?.addEventListener('click', _adDegistir); 150 kap.querySelector('#tpSil')?.addEventListener('click', _dagit); 151 kap.querySelector('#tpGrupEkle')?.addEventListener('click', _grupEkle); 152 _gruplariCiz(); 153} 154 155function _gruplariCiz() { 156 const kap = _host.querySelector('#tpGruplar'); 157 if (!kap) return; 158 const gruplar = _acik.groups || []; 159 if (!gruplar.length) { 160 kap.innerHTML = `<div style="font-size:12.5px;opacity:.7;">${ 161 escapeHtml(T('comm.no_groups', 'Henüz grup eklenmemiÅ.'))}</div>`; 162 return; 163 } 164 kap.innerHTML = gruplar.map(g => ` 165 <div style="display:flex;align-items:center;gap:10px;border-top:1px solid var(--border,#E6DEF0); 166 padding:8px 0;font-size:13px;"> 167 <span style="flex:1;cursor:pointer;" data-ac="${g.id}">${escapeHtml(g.title || '')}</span>
168 <span style="font-size:11.5px;opacity:.6;">${ 169 escapeHtml(T('comm.members_n', '%n kiÅi').replace('%n', g.member_count))}</span> 170 ${_acik.is_admin ? `<button type="button" data-cikar="${g.id}" 171 style="border:0;background:transparent;cursor:pointer;font:inherit;opacity:.6;" 172 title="${escapeHtml(T('comm.remove_group', 'Topluluktan çıkar'))}">Ã</button>` : ''} 173 </div>`).join(''); 174 kap.querySelectorAll('[data-ac]').forEach(el => { 175 el.addEventListener('click', () => { location.hash = `#messages/${el.dataset.ac}`; }); 176 }); 177 kap.querySelectorAll('[data-cikar]').forEach(b => { 178 b.addEventListener('click', async () => { 179 if (!confirm(T('comm.remove_group_q', 180 'Grup topluluktan çıkarılsın mı? Grup silinmez.'))) return; 181 try { 182 await api.del(`/api/communities/${_acik.id}/groups/${b.dataset.cikar}`); 183 await _yenile(); 184 } catch (e) { toast(e.message || ''); } 185 }); 186 }); 187} 188 189// ------------------------------------------------------------- eylemler 190 191async function _yeniTopluluk() { 192 const ad = prompt(T('comm.name_q', 'Topluluk adı:')); 193 if (!ad || !ad.trim()) return; 194 try { 195 _acik = await api.post('/api/communities', { name: ad.trim() }); 196 await _yenile(); 197 } catch (e) { toast(e.message || ''); } 198} 199 200async function _adDegistir() { 201 const ad = prompt(T('comm.name_q', 'Topluluk adı:'), _acik.name); 202 if (!ad || !ad.trim()) return; 203 try { 204 await api.patch(`/api/communities/${_acik.id}`, { name: ad.trim() }); 205 await _yenile(); 206 } catch (e) { toast(e.message || ''); } 207} 208 209async function _dagit() { 210 if (!confirm(T('comm.disband_q', 211 'Topluluk daÄıtılsın mı? Gruplar SİLİNMEZ, yalnız baÄları kopar; ' 212 + 'Duyurular grubu kapanır.'))) return; 213 try { 214 await api.del(`/api/communities/${_acik.id}`); 215 _acik = null; 216 await _yenile(); 217 } catch (e) { toast(e.message || ''); } 218} 219 220async function _grupEkle() { 221 // SEÃENEKLER SUNUCUNUN KABUL EDECEÄİ KÃME: yöneticisi olduÄun, henüz bir 222 // topluluÄa baÄlı olmayan gruplar. Tüm grupları listeleyip sunucuya 223 // reddettirmek, kullanıcıya her seferinde hata gösterirdi. 224 // 225 // KÃMEYİ SUNUCU HESAPLIYOR: burada "grupları çek, her biri için katılımcı 226 // ucunu çaÄır, rolüme bak" yapılıyordu â 30 gruplu kullanıcıda 31 istek. 227 // Ãstelik kural iki yerde yazılmıŠolurdu (bkz. topluluk.grup_ekle) ve 228 // biri deÄiÅince diÄeri sessizce ayrıÅırdı. 229 let adaylar = []; 230 try { 231 adaylar = (await api.get('/api/communities/aday-gruplar')).groups || []; 232 } catch (e) { toast(e.message || ''); return; } 233 234 if (!adaylar.length) { 235 toast(T('comm.no_candidates', 'Eklenebilecek grup yok.')); 236 return; 237 } 238 const satirlar = adaylar.map((g, i) => `${i + 1}. ${g.title || ''}`).join('\n'); 239 const cevap = prompt(`${T('comm.pick_group', 'Eklenecek grubun numarasını yazın:')}\n\n${satirlar}`); 240 const n = parseInt((cevap || '').trim(), 10); 241 if (!(n >= 1 && n <= adaylar.length)) return; 242 try { 243 await api.post(`/api/communities/${_acik.id}/groups`, 244 { conversation_id: adaylar[n - 1].id }); 245 await _yenile(); 246 } catch (e) { toast(e.message || ''); } 247}
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.