diff --git a/public/css/app.css b/public/css/app.css
index 78901de..43071a4 100644
--- a/public/css/app.css
+++ b/public/css/app.css
@@ -482,4 +482,21 @@ label { font-size: 13px; color: var(--text-dim); margin-bottom: 4px; display: bl
.onboarding-icon,
.onboarding-dot { animation: none !important; transition: none !important; }
.onboarding-slide { transform: none; opacity: 1; pointer-events: auto; }
-}
\ No newline at end of file
+}
+/* Pulse animation для важных кнопок (позвать на действие) */
+.btn-pulse {
+ display: inline-block;
+ animation: btn-pulse 2s ease-in-out infinite;
+}
+@keyframes btn-pulse {
+ 0%, 100% { transform: scale(1); }
+ 50% { transform: scale(1.2); }
+}
+
+/* Read-marks для чата */
+.bubble .meta { display: flex; align-items: center; gap: 4px; }
+.bubble .read-tick { font-size: 12px; opacity: 0.85; }
+.bubble .read-tick.read { color: var(--secondary); font-weight: 700; }
+
+/* edit marker */
+.bubble .edited { font-style: italic; opacity: 0.7; }
diff --git a/public/js/views/chat.js b/public/js/views/chat.js
index 0f85d3a..49d8a08 100644
--- a/public/js/views/chat.js
+++ b/public/js/views/chat.js
@@ -23,7 +23,7 @@ const ChatView = {
-
+
@@ -33,6 +33,14 @@ const ChatView = {
document.getElementById('chat-profile').onclick = () => App.openUserProfile(this.otherId, this.otherName);
document.getElementById('chat-review').onclick = () => App.openReview(this.chatId, this.otherId, this.otherName);
+ document.getElementById('msg-input').addEventListener('keydown', (e) => {
+ if (e.key === 'Enter' && !e.shiftKey) {
+ e.preventDefault();
+ this.send();
+ }
+ });
+ document.getElementById('msg-send').onclick = () => this.send();
+
this.loadMessages();
this.setupWS();
this.markRead();
@@ -59,11 +67,16 @@ const ChatView = {
if (m.deleted) {
div.innerHTML = 'Удалено';
} else {
+ // ✓✓ если прочитано собеседником, ✓ если только доставлено
+ const readMark = me
+ ? (m.read_at ? '✓✓' : '✓')
+ : '';
div.innerHTML = `
${escapeHtml(m.body)}
${formatTime(m.created_at)}
${m.edited ? 'ред.' : ''}
+ ${readMark}
`;
if (me) {
@@ -103,6 +116,8 @@ const ChatView = {
this.appendMessage(d.payload);
} else if (d.type === 'message_edited' && d.payload.chat_id === this.chatId) {
this.updateMessage(d.payload);
+ } else if (d.type === 'message_read' && d.payload.chat_id === this.chatId) {
+ this.markMessagesRead();
}
} catch {}
};
@@ -113,6 +128,18 @@ const ChatView = {
}
},
+ async markMessagesRead() {
+ try {
+ const r = await API.ChatApi.listMessages(this.chatId);
+ r.messages.forEach((m) => {
+ if (m.read_at) {
+ const el = document.querySelector(`.bubble[data-id="${m.id}"] .read-tick`);
+ if (el) { el.classList.add('read'); el.textContent = '✓✓'; }
+ }
+ });
+ } catch {}
+ },
+
updateMessage(p) {
const el = document.querySelector(`.bubble[data-id="${p.id}"]`);
if (el) {
@@ -134,7 +161,6 @@ const ChatView = {
inp.value = '';
try {
await API.ChatApi.sendMessage(this.chatId, body);
- // WS пришлёт обратно
} catch (e) {
TG_APP.showAlert('Ошибка: ' + e.message);
}
@@ -149,4 +175,4 @@ const ChatView = {
},
};
-window.ChatView = ChatView;
\ No newline at end of file
+window.ChatView = ChatView;
diff --git a/public/js/views/map.js b/public/js/views/map.js
index 1d7fe08..abdc359 100644
--- a/public/js/views/map.js
+++ b/public/js/views/map.js
@@ -21,7 +21,9 @@ const MapView = {
-
+
Нажми чтобы получить координаты
@@ -52,17 +54,21 @@ const MapView = {
this.visible = e.target.checked;
try {
await API.Auth.setVisibility(this.visible);
- this.refreshLocation();
+ if (window._lastKnownPos) this.applyPosition(window._lastKnownPos);
} catch (err) {
- API.Store && console.warn(err);
+ console.warn(err);
TG_APP.showAlert('Не удалось обновить видимость');
}
});
- document.getElementById('radius-sel').addEventListener('change', (e) => {
+ document.getElementById('radius-sel').addEventListener('change', async (e) => {
this.radius = parseInt(e.target.value, 10);
document.getElementById('radius-label').textContent = this.radius + ' км';
- this.refreshNearby();
+ const c = document.getElementById('nearby-count');
+ if (c) c.textContent = '⏳ поиск...';
+ if (window._lastKnownPos) {
+ await this.refreshNearbyFromPos(window._lastKnownPos.coords.latitude, window._lastKnownPos.coords.longitude);
+ }
});
document.getElementById('loc-share-btn').onclick = (e) => {
@@ -73,6 +79,10 @@ const MapView = {
// Если уже есть сохранённые координаты — покажем на карте
if (window._lastKnownPos) {
this.applyPosition(window._lastKnownPos);
+ } else {
+ // Авто-запрос геолокации при первом заходе на карту
+ // Не подвешиваем UI — requestLocation вернётся мгновенно, а браузер сам спросит у пользователя
+ this.requestLocation();
}
},
@@ -90,13 +100,24 @@ const MapView = {
navigator.geolocation.getCurrentPosition(
async (pos) => {
window._lastKnownPos = pos;
- if (btn) { btn.disabled = false; btn.textContent = '📍 Обновить геолокацию'; }
+ if (btn) {
+ btn.disabled = false;
+ btn.innerHTML = '📍 Обновить геолокацию';
+ }
if (status) status.textContent = `✓ ${pos.coords.latitude.toFixed(4)}, ${pos.coords.longitude.toFixed(4)}`;
await this.applyPosition(pos);
},
(err) => {
- if (btn) { btn.disabled = false; btn.textContent = '📍 Попробовать снова'; }
- if (status) status.textContent = '⚠️ ' + this.geolocationErrorText(err);
+ if (btn) {
+ btn.disabled = false;
+ btn.innerHTML = '📍 Попробовать снова';
+ }
+ if (status) {
+ const msg = this.geolocationErrorText(err);
+ status.innerHTML = '⚠️ ' + msg + (err.code === 1
+ ? '
Открой Настройки → Сайты → Геолокация → buhapp.mygoodservice.ru'
+ : '');
+ }
console.warn('geolocation error:', err);
},
{ enableHighAccuracy: true, timeout: 15000, maximumAge: 60000 }
@@ -105,7 +126,7 @@ const MapView = {
geolocationErrorText(err) {
switch (err.code) {
- case err.PERMISSION_DENIED: return 'Доступ к геолокации запрещён. Разреши в настройках браузера.';
+ case err.PERMISSION_DENIED: return 'Доступ к геолокации запрещён.';
case err.POSITION_UNAVAILABLE: return 'Не удалось определить координаты (нет GPS/Wi-Fi).';
case err.TIMEOUT: return 'Таймаут запроса геолокации.';
default: return 'Неизвестная ошибка геолокации.';
@@ -164,9 +185,11 @@ const MapView = {
this.markers.push(m);
});
} catch (e) {
+ const c = document.getElementById('nearby-count');
+ if (c) c.textContent = '📍 нужна геолокация';
console.warn(e);
}
},
};
-window.MapView = MapView;
\ No newline at end of file
+window.MapView = MapView;