PageSourceSearch

https://morfoz.ai/public/topluluklar.js?v=1790769371

js morfoz.ai collected 2026-10-05 15:08:45 UTC 10,737 bytes, 247 lines download raw bytes

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.