656 lines
26 KiB
PHP
656 lines
26 KiB
PHP
@extends('frontend.layouts.app')
|
||
@section('title', ($other?->name ?? 'Mesaj') . ' — Animexe')
|
||
@push('styles')
|
||
<style>
|
||
footer{display:none!important}
|
||
#page-content{padding:0!important}
|
||
#ai-fab,#ai-panel{display:none!important}
|
||
#topnav,#mob-bottom-nav{display:none!important}
|
||
.w-chat-support{display:none!important}
|
||
|
||
.chat-root{
|
||
display:flex;flex-direction:column;
|
||
position:fixed;
|
||
top:0;left:0;right:0;bottom:0;
|
||
background:#05050e;
|
||
}
|
||
|
||
/* ── Header ─────────────────────────────── */
|
||
.chat-hdr{
|
||
display:flex;align-items:center;gap:10px;
|
||
padding:10px 14px;
|
||
background:rgba(5,5,14,.95);backdrop-filter:blur(12px);
|
||
border-bottom:1px solid rgba(255,255,255,.07);
|
||
flex-shrink:0;z-index:10;
|
||
}
|
||
.chat-hdr-back{
|
||
width:36px;height:36px;border-radius:10px;
|
||
display:flex;align-items:center;justify-content:center;
|
||
background:rgba(255,255,255,.06);border:none;
|
||
color:rgba(255,255,255,.6);font-size:1rem;cursor:pointer;
|
||
text-decoration:none;flex-shrink:0;transition:all .15s;
|
||
}
|
||
.chat-hdr-back:hover{background:rgba(255,255,255,.12);color:#fff}
|
||
.chat-hdr-ava{
|
||
width:38px;height:38px;border-radius:50%;overflow:hidden;flex-shrink:0;
|
||
background:linear-gradient(135deg,var(--accent2),var(--accent));
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:.9rem;font-weight:800;color:#fff;text-decoration:none;
|
||
}
|
||
.chat-hdr-ava img{width:100%;height:100%;object-fit:cover}
|
||
.chat-hdr-info{flex:1;min-width:0}
|
||
.chat-hdr-name{font-size:.9rem;font-weight:800;color:#fff;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||
.chat-hdr-sub{font-size:.68rem;color:rgba(255,255,255,.3)}
|
||
.chat-hdr-btn{
|
||
width:36px;height:36px;border-radius:10px;flex-shrink:0;
|
||
display:flex;align-items:center;justify-content:center;
|
||
background:rgba(255,255,255,.06);border:none;color:rgba(255,255,255,.5);
|
||
font-size:.9rem;cursor:pointer;text-decoration:none;transition:all .15s;
|
||
}
|
||
.chat-hdr-btn:hover{background:rgba(255,255,255,.12);color:#fff}
|
||
.chat-hdr-btn.call{color:#22c55e}
|
||
.chat-hdr-btn.call:hover{background:rgba(34,197,94,.15);color:#22c55e}
|
||
|
||
/* ── Messages area ──────────────────────── */
|
||
.chat-body{
|
||
flex:1;overflow-y:auto;
|
||
padding:14px 14px 8px;
|
||
display:flex;flex-direction:column;
|
||
gap:4px;
|
||
-webkit-overflow-scrolling:touch;
|
||
min-height:0;
|
||
}
|
||
.chat-body::-webkit-scrollbar{width:3px}
|
||
.chat-body::-webkit-scrollbar-thumb{background:rgba(255,255,255,.1);border-radius:2px}
|
||
|
||
/* ── Bubbles ────────────────────────────── */
|
||
.chat-msg{display:flex;gap:8px;max-width:80%;align-items:flex-end}
|
||
.chat-msg.mine{align-self:flex-end;flex-direction:row-reverse}
|
||
.chat-msg.theirs{align-self:flex-start}
|
||
.chat-msg-ava{
|
||
width:28px;height:28px;border-radius:50%;overflow:hidden;flex-shrink:0;
|
||
background:linear-gradient(135deg,var(--accent2),var(--accent));
|
||
display:flex;align-items:center;justify-content:center;
|
||
font-size:.65rem;font-weight:800;color:#fff;
|
||
}
|
||
.chat-msg-ava img{width:100%;height:100%;object-fit:cover}
|
||
.chat-msg.mine .chat-msg-ava{display:none}
|
||
.chat-bubble{
|
||
padding:9px 14px;border-radius:18px;
|
||
font-size:.88rem;line-height:1.5;word-break:break-word;max-width:100%;
|
||
}
|
||
.chat-msg.mine .chat-bubble{
|
||
background:var(--accent2);color:#05050e;font-weight:500;border-bottom-right-radius:5px;
|
||
}
|
||
.chat-msg.theirs .chat-bubble{
|
||
background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.08);
|
||
color:rgba(255,255,255,.9);border-bottom-left-radius:5px;
|
||
}
|
||
.chat-time{font-size:.6rem;color:rgba(255,255,255,.25);align-self:flex-end;margin-bottom:3px;flex-shrink:0}
|
||
.chat-date-sep{
|
||
text-align:center;font-size:.65rem;color:rgba(255,255,255,.2);
|
||
padding:12px 0 4px;font-weight:600;letter-spacing:.08em;
|
||
}
|
||
|
||
/* ── Media messages ─────────────────────── */
|
||
.chat-img{
|
||
max-width:220px;max-height:220px;border-radius:14px;
|
||
cursor:pointer;display:block;object-fit:cover;
|
||
transition:opacity .15s;border:1px solid rgba(255,255,255,.08);
|
||
}
|
||
.chat-img:hover{opacity:.88}
|
||
.chat-gif{max-width:220px;border-radius:14px;display:block}
|
||
|
||
/* ── Share card ─────────────────────────── */
|
||
.share-card{
|
||
display:flex;gap:10px;align-items:flex-start;
|
||
background:rgba(0,245,255,.06);border:1px solid rgba(0,245,255,.18);
|
||
border-radius:14px;padding:10px 12px;text-decoration:none;
|
||
max-width:260px;transition:all .15s;
|
||
}
|
||
.share-card:hover{background:rgba(0,245,255,.12);border-color:rgba(0,245,255,.35)}
|
||
.share-card-img{width:54px;height:78px;border-radius:8px;overflow:hidden;flex-shrink:0;background:rgba(255,255,255,.05)}
|
||
.share-card-img img{width:100%;height:100%;object-fit:cover;display:block}
|
||
.share-card-label{font-size:.58rem;color:var(--accent2);font-weight:700;letter-spacing:.08em;text-transform:uppercase;margin-bottom:4px}
|
||
.share-card-title{font-size:.8rem;font-weight:800;color:#fff;line-height:1.3;margin-bottom:3px;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}
|
||
.share-card-ep{font-size:.7rem;color:rgba(255,255,255,.5);margin-bottom:3px}
|
||
.share-card-play{font-size:.68rem;color:var(--accent2);display:flex;align-items:center;gap:3px;margin-top:4px;font-weight:700}
|
||
|
||
/* ── Footer input ───────────────────────── */
|
||
.chat-footer{
|
||
padding:8px 10px;
|
||
background:rgba(5,5,14,.95);backdrop-filter:blur(12px);
|
||
border-top:1px solid rgba(255,255,255,.07);
|
||
flex-shrink:0;
|
||
padding-bottom:max(8px,env(safe-area-inset-bottom));
|
||
}
|
||
.chat-toolbar{
|
||
display:flex;align-items:center;gap:6px;
|
||
padding:4px 0 8px;
|
||
border-bottom:1px solid rgba(255,255,255,.06);
|
||
margin-bottom:6px;
|
||
}
|
||
.chat-tool-btn{
|
||
width:38px;height:38px;border-radius:10px;border:none;
|
||
background:rgba(255,255,255,.06);color:rgba(255,255,255,.45);
|
||
font-size:.88rem;cursor:pointer;
|
||
display:flex;align-items:center;justify-content:center;
|
||
transition:all .15s;flex-shrink:0;
|
||
-webkit-tap-highlight-color:transparent;
|
||
}
|
||
.chat-tool-btn:hover{background:rgba(255,255,255,.13);color:#fff}
|
||
.chat-tool-btn.active{background:rgba(0,245,255,.12);color:var(--accent2)}
|
||
.chat-form{display:flex;align-items:flex-end;gap:8px}
|
||
.chat-input{
|
||
flex:1;background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.1);
|
||
border-radius:20px;padding:10px 16px;color:#fff;font-size:.9rem;font-family:inherit;
|
||
resize:none;max-height:120px;overflow-y:auto;min-height:42px;
|
||
line-height:1.4;outline:none;-webkit-appearance:none;transition:border-color .15s;
|
||
}
|
||
.chat-input:focus{border-color:rgba(0,245,255,.35)}
|
||
.chat-input::placeholder{color:rgba(255,255,255,.25)}
|
||
.chat-send{
|
||
width:42px;height:42px;border-radius:50%;flex-shrink:0;
|
||
background:var(--accent2);border:none;color:#05050e;
|
||
font-size:1rem;cursor:pointer;
|
||
display:flex;align-items:center;justify-content:center;
|
||
transition:all .15s;-webkit-tap-highlight-color:transparent;
|
||
}
|
||
.chat-send:hover{background:#00d4de;transform:scale(1.05)}
|
||
.chat-send:disabled{opacity:.4;transform:none;cursor:default}
|
||
|
||
/* ── Emoji picker ───────────────────────── */
|
||
#chat-emoji-picker{
|
||
display:none;flex-wrap:wrap;gap:2px;
|
||
background:rgba(10,10,22,.98);border:1px solid rgba(255,255,255,.1);
|
||
border-radius:14px;padding:10px;
|
||
max-height:180px;overflow-y:auto;margin-bottom:6px;
|
||
scrollbar-width:thin;
|
||
}
|
||
#chat-emoji-picker.show{display:flex}
|
||
.chat-emoji-btn{
|
||
font-size:1.3rem;width:36px;height:36px;
|
||
display:flex;align-items:center;justify-content:center;
|
||
border:none;background:none;cursor:pointer;border-radius:8px;
|
||
transition:background .12s;-webkit-tap-highlight-color:transparent;
|
||
}
|
||
.chat-emoji-btn:hover{background:rgba(255,255,255,.1)}
|
||
|
||
/* ── GIF picker ─────────────────────────── */
|
||
#chat-gif-picker{
|
||
display:none;
|
||
background:rgba(10,10,22,.98);border:1px solid rgba(255,255,255,.1);
|
||
border-radius:14px;padding:8px;margin-bottom:6px;
|
||
}
|
||
#chat-gif-picker.show{display:block}
|
||
.chat-gif-search{
|
||
width:100%;padding:8px 12px;border-radius:10px;
|
||
background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.12);
|
||
color:#fff;font-size:.82rem;font-family:inherit;outline:none;
|
||
margin-bottom:8px;-webkit-appearance:none;
|
||
}
|
||
.chat-gif-search::placeholder{color:rgba(255,255,255,.3)}
|
||
.chat-gif-grid{
|
||
display:grid;grid-template-columns:repeat(3,1fr);gap:4px;
|
||
max-height:160px;overflow-y:auto;
|
||
}
|
||
.chat-gif-item{
|
||
aspect-ratio:1;background:rgba(255,255,255,.05);border-radius:8px;
|
||
overflow:hidden;cursor:pointer;transition:opacity .15s;
|
||
}
|
||
.chat-gif-item:hover{opacity:.8}
|
||
.chat-gif-item img{width:100%;height:100%;object-fit:cover;display:block}
|
||
.chat-gif-ph{padding:20px;text-align:center;color:rgba(255,255,255,.25);font-size:.78rem}
|
||
|
||
.chat-empty{
|
||
flex:1;display:flex;flex-direction:column;align-items:center;justify-content:center;
|
||
color:rgba(255,255,255,.2);gap:10px;
|
||
}
|
||
.chat-empty i{font-size:2.4rem;opacity:.2}
|
||
.chat-empty p{font-size:.85rem}
|
||
|
||
@media(min-width:640px){
|
||
.chat-root{max-width:760px;left:50%;transform:translateX(-50%)}
|
||
.chat-msg{max-width:72%}
|
||
}
|
||
@media(max-width:640px){
|
||
.chat-msg{max-width:88%}
|
||
}
|
||
</style>
|
||
@endpush
|
||
|
||
@section('content')
|
||
@php $me = auth()->user(); @endphp
|
||
<div class="chat-root">
|
||
|
||
{{-- Header --}}
|
||
<div class="chat-hdr">
|
||
<a href="{{ route('messages.index') }}" class="chat-hdr-back">
|
||
<i class="bi bi-chevron-left"></i>
|
||
</a>
|
||
<a href="{{ $other ? route('user.profile', $other) : '#' }}" class="chat-hdr-ava">
|
||
@if($other?->avatar)
|
||
<img src="{{ \App\Support\MediaUrl::fromStoragePath($other->avatar) }}" alt="{{ $other->name }}">
|
||
@else
|
||
{{ mb_strtoupper(mb_substr($other?->name ?? '?', 0, 1)) }}
|
||
@endif
|
||
</a>
|
||
<div class="chat-hdr-info">
|
||
<div class="chat-hdr-name">{{ $other?->name ?? 'Silinmiş Kullanıcı' }}</div>
|
||
@if($other?->username)
|
||
<div class="chat-hdr-sub">@php echo '@' . e($other->username); @endphp</div>
|
||
@endif
|
||
</div>
|
||
@if($other)
|
||
{{-- Sesli arama --}}
|
||
<button class="chat-hdr-btn call" id="call-btn" data-user-id="{{ $other->id }}" title="Sesli ara">
|
||
<i class="bi bi-telephone-fill"></i>
|
||
</button>
|
||
{{-- Profil --}}
|
||
<a href="{{ route('user.profile', $other) }}" class="chat-hdr-btn" title="Profil">
|
||
<i class="bi bi-person"></i>
|
||
</a>
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Messages --}}
|
||
<div class="chat-body" id="chat-body">
|
||
@if($messages->isEmpty())
|
||
<div class="chat-empty">
|
||
<i class="bi bi-chat-dots"></i>
|
||
<p>Henüz mesaj yok. İlk mesajı sen gönder!</p>
|
||
</div>
|
||
@else
|
||
@php $lastDate = null; @endphp
|
||
@foreach($messages as $msg)
|
||
@php
|
||
$msgDate = $msg->created_at->toDateString();
|
||
$isMine = $msg->user_id === $me->id;
|
||
@endphp
|
||
@if($msgDate !== $lastDate)
|
||
<div class="chat-date-sep">{{ $msg->created_at->translatedFormat('d F Y') }}</div>
|
||
@php $lastDate = $msgDate; @endphp
|
||
@endif
|
||
<div class="chat-msg {{ $isMine ? 'mine' : 'theirs' }}" data-id="{{ $msg->id }}">
|
||
@if(!$isMine)
|
||
<div class="chat-msg-ava">
|
||
@if($msg->user?->avatar)
|
||
<img src="{{ \App\Support\MediaUrl::fromStoragePath($msg->user->avatar) }}" alt="">
|
||
@else{{ mb_strtoupper(mb_substr($msg->user?->name ?? '?', 0, 1)) }}@endif
|
||
</div>
|
||
@endif
|
||
<div>
|
||
@if(str_starts_with($msg->body, 'ANIMESHARE::'))
|
||
@php try { $sd = json_decode(substr($msg->body,12),true); } catch(\Throwable $e){ $sd=null; } @endphp
|
||
@if($sd)
|
||
<a href="{{ url('/watch/'.($sd['slug']??'').'/'.($sd['s']??1).'/'.($sd['e']??1).(($sd['ts']??0)>0?'?t='.$sd['ts']:'')) }}" class="share-card">
|
||
<div class="share-card-img">
|
||
@if(!empty($sd['cover']))<img src="{{ $sd['cover'] }}" loading="lazy" alt="">@else<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,.2);font-size:1.4rem"><i class="bi bi-play-circle"></i></div>@endif
|
||
</div>
|
||
<div>
|
||
<div class="share-card-label"><i class="bi bi-play-fill"></i> Anime Bölümü</div>
|
||
<div class="share-card-title">{{ $sd['title'] ?? '' }}</div>
|
||
<div class="share-card-ep">{{ $sd['s'] ?? '' }}. Sezon {{ $sd['e'] ?? '' }}. Bölüm</div>
|
||
<div class="share-card-play"><i class="bi bi-play-circle-fill"></i> İzle</div>
|
||
</div>
|
||
</a>
|
||
@else
|
||
<div class="chat-bubble">{{ $msg->body }}</div>
|
||
@endif
|
||
@elseif(str_starts_with($msg->body, 'IMAGE::'))
|
||
<img src="{{ substr($msg->body, 7) }}" class="chat-img" loading="lazy" alt="Resim"
|
||
onclick="window.open('{{ substr($msg->body, 7) }}','_blank')">
|
||
@elseif(str_starts_with($msg->body, 'GIF::'))
|
||
<img src="{{ substr($msg->body, 5) }}" class="chat-gif" loading="lazy" alt="GIF">
|
||
@else
|
||
<div class="chat-bubble">{{ $msg->body }}</div>
|
||
@endif
|
||
</div>
|
||
<span class="chat-time">{{ $msg->created_at->format('H:i') }}</span>
|
||
</div>
|
||
@endforeach
|
||
@endif
|
||
</div>
|
||
|
||
{{-- Footer --}}
|
||
<div class="chat-footer">
|
||
<div id="chat-emoji-picker"></div>
|
||
<div id="chat-gif-picker">
|
||
<input type="text" class="chat-gif-search" id="chat-gif-input" placeholder="GIF ara…">
|
||
<div class="chat-gif-grid" id="chat-gif-grid">
|
||
<div class="chat-gif-ph">GIF aramak için yaz</div>
|
||
</div>
|
||
</div>
|
||
<div class="chat-toolbar">
|
||
<button class="chat-tool-btn" id="chat-emoji-btn" title="Emoji" style="display:none">
|
||
<i class="bi bi-emoji-smile"></i>
|
||
</button>
|
||
<button class="chat-tool-btn" id="chat-gif-btn" title="GIF">
|
||
<span style="font-size:.65rem;font-weight:800;letter-spacing:.03em">GIF</span>
|
||
</button>
|
||
<label class="chat-tool-btn" title="Resim gönder" style="cursor:pointer">
|
||
<i class="bi bi-image"></i>
|
||
<input type="file" id="chat-img-input" accept="image/*" style="display:none">
|
||
</label>
|
||
<div style="flex:1"></div>
|
||
</div>
|
||
<div class="chat-form">
|
||
<textarea class="chat-input" id="chat-input"
|
||
placeholder="Mesaj yaz..." rows="1" maxlength="2000"
|
||
autocomplete="off" autocorrect="off"></textarea>
|
||
<button type="button" class="chat-send" id="chat-send">
|
||
<i class="bi bi-send-fill"></i>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
@endsection
|
||
|
||
@push('scripts')
|
||
<script>
|
||
// Crisp ve diğer overlay widgetları kapat
|
||
try { $crisp.push(['do','chat:hide']); } catch(_){}
|
||
document.addEventListener('DOMContentLoaded', function(){
|
||
try { $crisp.push(['do','chat:hide']); } catch(_){}
|
||
// topnav/bottom-nav kesinlikle gizle (layout JS override için)
|
||
const tn = document.getElementById('topnav');
|
||
const bn = document.getElementById('mob-bottom-nav');
|
||
if(tn) tn.style.setProperty('display','none','important');
|
||
if(bn) bn.style.setProperty('display','none','important');
|
||
});
|
||
</script>
|
||
<script>
|
||
(function(){
|
||
'use strict';
|
||
|
||
const SEND_URL = '{{ route("messages.send", $conversation) }}';
|
||
const POLL_URL = '{{ route("messages.poll", $conversation) }}';
|
||
const UPLOAD_URL = '{{ route("messages.upload-image") }}';
|
||
const TENOR_KEY = '{{ env("TENOR_API_KEY","") }}';
|
||
const CSRF = document.querySelector('meta[name=csrf-token]')?.content || '';
|
||
const ME_ID = {{ $me->id }};
|
||
const MY_AVA = {!! json_encode($me->avatar ? \App\Support\MediaUrl::fromStoragePath($me->avatar) : null) !!};
|
||
const MY_NAME = {!! json_encode($me->name) !!};
|
||
const IS_DESKTOP = window.innerWidth >= 640;
|
||
|
||
let lastId = {{ $messages->last()?->id ?? 0 }};
|
||
let polling = null;
|
||
let emojiOpen = false;
|
||
let gifOpen = false;
|
||
let gifTimer = null;
|
||
|
||
const chatBody = document.getElementById('chat-body');
|
||
const inputEl = document.getElementById('chat-input');
|
||
const sendBtn = document.getElementById('chat-send');
|
||
|
||
/* ── Scroll ──────────────────────────────── */
|
||
function scrollBottom(force = false) {
|
||
const near = chatBody.scrollHeight - chatBody.scrollTop - chatBody.clientHeight < 160;
|
||
if (force || near) chatBody.scrollTop = chatBody.scrollHeight;
|
||
}
|
||
scrollBottom(true);
|
||
|
||
/* ── Input auto-grow ─────────────────────── */
|
||
inputEl.addEventListener('input', () => {
|
||
inputEl.style.height = 'auto';
|
||
inputEl.style.height = Math.min(inputEl.scrollHeight, 120) + 'px';
|
||
if (inputEl.value) { hideEmoji(); hideGif(); }
|
||
});
|
||
inputEl.addEventListener('keydown', e => {
|
||
if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); doSend(); }
|
||
});
|
||
sendBtn.addEventListener('click', doSend);
|
||
|
||
/* ── Escape chars ────────────────────────── */
|
||
function escH(s) {
|
||
return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');
|
||
}
|
||
|
||
/* ── Render message body ─────────────────── */
|
||
function renderBody(body) {
|
||
if (!body) return '<div class="chat-bubble"></div>';
|
||
if (body.startsWith('ANIMESHARE::')) {
|
||
try {
|
||
const d = JSON.parse(body.slice(12));
|
||
const url = `/watch/${d.slug}/${d.s}/${d.e}${d.ts>0?'?t='+d.ts:''}`;
|
||
const img = d.cover
|
||
? `<img src="${escH(d.cover)}" loading="lazy" alt="">`
|
||
: `<div style="width:100%;height:100%;display:flex;align-items:center;justify-content:center;color:rgba(255,255,255,.2)"><i class="bi bi-play-circle" style="font-size:1.4rem"></i></div>`;
|
||
return `<a href="${escH(url)}" class="share-card">
|
||
<div class="share-card-img">${img}</div>
|
||
<div>
|
||
<div class="share-card-label"><i class="bi bi-play-fill"></i> Anime Bölümü</div>
|
||
<div class="share-card-title">${escH(d.title||'')}</div>
|
||
<div class="share-card-ep">${escH(String(d.s))}. Sezon ${escH(String(d.e))}. Bölüm</div>
|
||
<div class="share-card-play"><i class="bi bi-play-circle-fill"></i> İzle</div>
|
||
</div>
|
||
</a>`;
|
||
} catch(_) {}
|
||
}
|
||
if (body.startsWith('IMAGE::')) {
|
||
const url = escH(body.slice(7));
|
||
return `<img src="${url}" class="chat-img" loading="lazy" alt="Resim" onclick="window.open('${url}','_blank')">`;
|
||
}
|
||
if (body.startsWith('GIF::')) {
|
||
return `<img src="${escH(body.slice(5))}" class="chat-gif" loading="lazy" alt="GIF">`;
|
||
}
|
||
return `<div class="chat-bubble">${escH(body)}</div>`;
|
||
}
|
||
|
||
/* ── Append message to DOM ───────────────── */
|
||
function appendMsg(id, body, isMine, time, avatar, name) {
|
||
if (document.querySelector(`.chat-msg[data-id="${id}"]`)) return;
|
||
document.querySelector('.chat-empty')?.remove();
|
||
|
||
const wrap = document.createElement('div');
|
||
wrap.className = 'chat-msg ' + (isMine ? 'mine' : 'theirs');
|
||
wrap.dataset.id = id;
|
||
|
||
let ava = '';
|
||
if (!isMine) {
|
||
ava = avatar
|
||
? `<div class="chat-msg-ava"><img src="${escH(avatar)}" alt=""></div>`
|
||
: `<div class="chat-msg-ava">${escH((name||'?').charAt(0).toUpperCase())}</div>`;
|
||
}
|
||
wrap.innerHTML = ava + `<div>${renderBody(body)}</div><span class="chat-time">${escH(time)}</span>`;
|
||
chatBody.appendChild(wrap);
|
||
scrollBottom();
|
||
}
|
||
|
||
/* ── Send text ───────────────────────────── */
|
||
async function doSend() {
|
||
const text = inputEl.value.trim();
|
||
if (!text) return;
|
||
sendBtn.disabled = true;
|
||
inputEl.value = '';
|
||
inputEl.style.height = 'auto';
|
||
|
||
try {
|
||
const res = await fetch(SEND_URL, {
|
||
method: 'POST',
|
||
headers: {'X-CSRF-TOKEN': CSRF, 'Accept': 'application/json', 'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ body: text }),
|
||
});
|
||
if (!res.ok) throw new Error();
|
||
const d = await res.json();
|
||
appendMsg(d.id, d.body, true, d.created_at, MY_AVA, MY_NAME);
|
||
if (d.id > lastId) lastId = d.id;
|
||
scrollBottom(true);
|
||
} catch(e) {
|
||
inputEl.value = text;
|
||
}
|
||
sendBtn.disabled = false;
|
||
inputEl.focus();
|
||
}
|
||
|
||
/* ── Send image ──────────────────────────── */
|
||
document.getElementById('chat-img-input')?.addEventListener('change', async function() {
|
||
const file = this.files[0];
|
||
if (!file) return;
|
||
this.value = '';
|
||
try {
|
||
const fd = new FormData();
|
||
fd.append('image', file);
|
||
const r = await fetch(UPLOAD_URL, {
|
||
method: 'POST',
|
||
headers: {'X-CSRF-TOKEN': CSRF, 'Accept': 'application/json'},
|
||
body: fd,
|
||
});
|
||
const d = await r.json();
|
||
if (!d.url) throw new Error();
|
||
const r2 = await fetch(SEND_URL, {
|
||
method: 'POST',
|
||
headers: {'X-CSRF-TOKEN': CSRF, 'Accept': 'application/json', 'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ body: 'IMAGE::' + d.url }),
|
||
});
|
||
const d2 = await r2.json();
|
||
appendMsg(d2.id, d2.body, true, d2.created_at, MY_AVA, MY_NAME);
|
||
if (d2.id > lastId) lastId = d2.id;
|
||
scrollBottom(true);
|
||
} catch(e) {
|
||
if (window.showToast) showToast('Resim gönderilemedi.', 'error');
|
||
}
|
||
});
|
||
|
||
/* ── Send GIF ────────────────────────────── */
|
||
window.chatSendGif = async function(gifUrl) {
|
||
hideGif();
|
||
try {
|
||
const r = await fetch(SEND_URL, {
|
||
method: 'POST',
|
||
headers: {'X-CSRF-TOKEN': CSRF, 'Accept': 'application/json', 'Content-Type': 'application/json'},
|
||
body: JSON.stringify({ body: 'GIF::' + gifUrl }),
|
||
});
|
||
const d = await r.json();
|
||
appendMsg(d.id, d.body, true, d.created_at, MY_AVA, MY_NAME);
|
||
if (d.id > lastId) lastId = d.id;
|
||
scrollBottom(true);
|
||
} catch(_) {}
|
||
};
|
||
|
||
/* ── Polling ─────────────────────────────── */
|
||
async function poll() {
|
||
try {
|
||
const res = await fetch(`${POLL_URL}?after=${lastId}`, {
|
||
headers: {'Accept': 'application/json', 'X-CSRF-TOKEN': CSRF}
|
||
});
|
||
const d = await res.json();
|
||
d.messages.forEach(m => {
|
||
if (m.user_id !== ME_ID) {
|
||
appendMsg(m.id, m.body, false, m.created_at, m.avatar, m.name);
|
||
}
|
||
if (m.id > lastId) lastId = m.id;
|
||
});
|
||
} catch(_) {}
|
||
}
|
||
|
||
function startPoll() { polling = setInterval(poll, 3000); }
|
||
function stopPoll() { clearInterval(polling); polling = null; }
|
||
document.addEventListener('visibilitychange', () => document.hidden ? stopPoll() : startPoll());
|
||
startPoll();
|
||
|
||
/* ── Emoji picker ────────────────────────── */
|
||
const EMOJIS = '😀😂😅😊😍🥰😘😎😭😤😡🥺🤔😴🤩🥳😱🙈🙄😏🤗😬😵👍👎❤️🔥💯✨🎉🎊🎮🎬🎵🍕🍜🍣🎃👻🦊🐱🐶🐼🌸🌙⭐🌈💎🚀🏆🎯🔮';
|
||
|
||
function initEmoji() {
|
||
const p = document.getElementById('chat-emoji-picker');
|
||
if (!p || p.dataset.built) return;
|
||
p.dataset.built = '1';
|
||
[...EMOJIS].forEach(em => {
|
||
const btn = document.createElement('button');
|
||
btn.className = 'chat-emoji-btn';
|
||
btn.textContent = em;
|
||
btn.addEventListener('click', () => {
|
||
const pos = inputEl.selectionStart;
|
||
inputEl.value = inputEl.value.slice(0, pos) + em + inputEl.value.slice(inputEl.selectionEnd);
|
||
inputEl.selectionStart = inputEl.selectionEnd = pos + em.length;
|
||
inputEl.dispatchEvent(new Event('input'));
|
||
inputEl.focus();
|
||
});
|
||
p.appendChild(btn);
|
||
});
|
||
}
|
||
|
||
function toggleEmoji() {
|
||
emojiOpen = !emojiOpen;
|
||
if (emojiOpen) { hideGif(); initEmoji(); }
|
||
document.getElementById('chat-emoji-picker').classList.toggle('show', emojiOpen);
|
||
document.getElementById('chat-emoji-btn').classList.toggle('active', emojiOpen);
|
||
}
|
||
function hideEmoji() {
|
||
emojiOpen = false;
|
||
document.getElementById('chat-emoji-picker')?.classList.remove('show');
|
||
document.getElementById('chat-emoji-btn')?.classList.remove('active');
|
||
}
|
||
|
||
document.getElementById('chat-emoji-btn')?.addEventListener('click', toggleEmoji);
|
||
|
||
// Desktop'ta emoji butonu göster
|
||
if (IS_DESKTOP) {
|
||
const eb = document.getElementById('chat-emoji-btn');
|
||
if (eb) eb.style.display = '';
|
||
}
|
||
|
||
/* ── GIF picker ──────────────────────────── */
|
||
async function searchGifs(q) {
|
||
const grid = document.getElementById('chat-gif-grid');
|
||
if (!grid) return;
|
||
if (!TENOR_KEY) {
|
||
grid.innerHTML = '<div class="chat-gif-ph">Tenor API key ayarlanmamış.</div>';
|
||
return;
|
||
}
|
||
grid.innerHTML = '<div class="chat-gif-ph">Yükleniyor…</div>';
|
||
try {
|
||
const ep = q
|
||
? `https://tenor.googleapis.com/v2/search?q=${encodeURIComponent(q)}&key=${TENOR_KEY}&limit=12&media_filter=gif`
|
||
: `https://tenor.googleapis.com/v2/featured?key=${TENOR_KEY}&limit=12&media_filter=gif`;
|
||
const r = await fetch(ep);
|
||
const d = await r.json();
|
||
const results = d.results || [];
|
||
if (!results.length) { grid.innerHTML = '<div class="chat-gif-ph">Sonuç yok.</div>'; return; }
|
||
grid.innerHTML = '';
|
||
results.forEach(g => {
|
||
const url = g.media_formats?.tinygif?.url || g.media_formats?.gif?.url || '';
|
||
if (!url) return;
|
||
const item = document.createElement('div');
|
||
item.className = 'chat-gif-item';
|
||
item.innerHTML = `<img src="${escH(url)}" alt="" loading="lazy">`;
|
||
item.addEventListener('click', () => window.chatSendGif(url));
|
||
grid.appendChild(item);
|
||
});
|
||
} catch(_) { grid.innerHTML = '<div class="chat-gif-ph">Yüklenemedi.</div>'; }
|
||
}
|
||
|
||
function toggleGif() {
|
||
gifOpen = !gifOpen;
|
||
if (gifOpen) { hideEmoji(); if (TENOR_KEY) searchGifs(''); setTimeout(() => document.getElementById('chat-gif-input')?.focus(), 80); }
|
||
document.getElementById('chat-gif-picker').classList.toggle('show', gifOpen);
|
||
document.getElementById('chat-gif-btn').classList.toggle('active', gifOpen);
|
||
}
|
||
function hideGif() {
|
||
gifOpen = false;
|
||
document.getElementById('chat-gif-picker')?.classList.remove('show');
|
||
document.getElementById('chat-gif-btn')?.classList.remove('active');
|
||
}
|
||
|
||
document.getElementById('chat-gif-btn')?.addEventListener('click', toggleGif);
|
||
document.getElementById('chat-gif-input')?.addEventListener('input', function() {
|
||
clearTimeout(gifTimer);
|
||
gifTimer = setTimeout(() => searchGifs(this.value.trim()), 380);
|
||
});
|
||
|
||
/* ── Sesli arama ─────────────────────────── */
|
||
document.getElementById('call-btn')?.addEventListener('click', async function() {
|
||
const userId = this.dataset.userId;
|
||
if (!userId || !window.cpStartCall) { if (window.showToast) showToast('Arama özelliği yüklenemedi.','error'); return; }
|
||
window.cpConvUserId = userId;
|
||
window.cpStartCall();
|
||
});
|
||
|
||
/* ── Auto focus ──────────────────────────── */
|
||
if (IS_DESKTOP) inputEl.focus();
|
||
|
||
})();
|
||
</script>
|
||
@endpush
|