From af62b51ef57acc5168e7adc32eecb9e005ff8a729dc9ac3341694d8e7edee3a2 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Wed, 22 Jul 2026 18:59:09 +0400 Subject: [PATCH] =?UTF-8?q?UI:=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=87=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D0=B0=20=D0=B8=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=BE=D0=B5?= =?UTF-8?q?=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VERSION.properties | 4 +- shine-UI/js/components/header.js | 8 +++- shine-UI/js/pages/chat-view.js | 61 ++++++++++++++++-------------- shine-UI/js/pages/settings-view.js | 5 --- shine-UI/styles/components.css | 58 ++++++++++++++++++++++------ 5 files changed, 88 insertions(+), 48 deletions(-) diff --git a/VERSION.properties b/VERSION.properties index 0af54c47..2e03e5a2 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.342 -server.version=1.2.314 +client.version=1.2.343 +server.version=1.2.315 diff --git a/shine-UI/js/components/header.js b/shine-UI/js/components/header.js index ead1a756..a3337646 100644 --- a/shine-UI/js/components/header.js +++ b/shine-UI/js/components/header.js @@ -6,6 +6,7 @@ export function renderHeader({ title, leftAction, leftLabel = '', rightActions = left.className = 'header-left'; if (leftAction) { const btn = document.createElement('button'); + btn.type = 'button'; btn.className = 'icon-btn'; btn.textContent = leftAction.label; btn.addEventListener('click', leftAction.onClick); @@ -26,10 +27,15 @@ export function renderHeader({ title, leftAction, leftLabel = '', rightActions = right.className = 'header-actions'; rightActions.forEach((action) => { const btn = document.createElement('button'); + btn.type = 'button'; btn.className = `icon-btn${action.className ? ` ${action.className}` : ''}`; - btn.textContent = action.label; if (action.title) btn.title = action.title; if (action.ariaLabel) btn.setAttribute('aria-label', action.ariaLabel); + if (action.iconNode instanceof Node) { + btn.append(action.iconNode); + } else { + btn.textContent = action.label; + } btn.addEventListener('click', action.onClick); right.append(btn); }); diff --git a/shine-UI/js/pages/chat-view.js b/shine-UI/js/pages/chat-view.js index 3b4c718f..3cddbf49 100644 --- a/shine-UI/js/pages/chat-view.js +++ b/shine-UI/js/pages/chat-view.js @@ -24,8 +24,7 @@ import { hasTwemojiAsset, stopAllTwemojiAnimations, } from '../components/emoji-picker.js?v=202607152130'; -import { openSpeechInputModal } from '../components/speech-input-modal.js'; -import { isSpeechToTextConfigured, isTextToSpeechConfigured, speakTextBySettings } from '../services/speech-tools-service.js'; +import { isTextToSpeechConfigured, speakTextBySettings } from '../services/speech-tools-service.js'; import { showToast } from '../services/channels-ux.js'; import { buildDmReplyTechBlock, parseDmTechBlocks, sanitizeUserDmTextForSend } from '../services/dm-tech-blocks.js'; @@ -343,6 +342,20 @@ function autoResizeComposer(textarea) { textarea.style.height = `${Math.min(180, Math.max(42, textarea.scrollHeight))}px`; } +function createHeaderPhoneHandsetIcon() { + const ns = 'http://www.w3.org/2000/svg'; + const svg = document.createElementNS(ns, 'svg'); + svg.setAttribute('viewBox', '0 0 24 24'); + svg.setAttribute('aria-hidden', 'true'); + svg.setAttribute('class', 'header-icon-svg header-icon-svg--phone'); + + const path = document.createElementNS(ns, 'path'); + path.setAttribute('d', 'M7.62 10.79a15.1 15.1 0 0 0 5.59 5.59l1.87-1.87a1 1 0 0 1 1.02-.24c1.12.37 2.31.57 3.52.57a1 1 0 0 1 1 1V20a1 1 0 0 1-1 1C10.61 21 3 13.39 3 4a1 1 0 0 1 1-1h3.37a1 1 0 0 1 1 1c0 1.21.2 2.4.57 3.52a1 1 0 0 1-.24 1.02l-1.08 1.27Z'); + path.setAttribute('fill', 'currentColor'); + svg.append(path); + return svg; +} + function openConfirmContactModal(targetLogin = '') { const root = document.getElementById('modal-root'); if (!root) return Promise.resolve(false); @@ -762,7 +775,6 @@ export function render({ navigate, route }) { const screen = document.createElement('section'); screen.className = 'stack dm-screen dm-chat-screen'; - const isSpeechToTextReady = isSpeechToTextConfigured(state.entrySettings); const isTextToSpeechReady = isTextToSpeechConfigured(state.entrySettings); const isKnownContact = (state.contacts || []).some((x) => String(x || '').toLowerCase() === String(chatId || '').toLowerCase()); const hasUnreadIncoming = getChatMessages(chatId).some((msg) => msg?.from === 'in' && msg?.unread); @@ -834,10 +846,10 @@ export function render({ navigate, route }) { leftAction: { label: '←', onClick: () => navigate('messages-list') }, rightActions: [ { - label: '☎', title: 'Позвонить', ariaLabel: 'Позвонить', className: 'chat-header-icon-btn chat-header-call-btn', + iconNode: createHeaderPhoneHandsetIcon(), onClick: handleStartCall, }, { @@ -948,8 +960,7 @@ export function render({ navigate, route }) {
- - ${isSpeechToTextReady ? '' : ''} +
`; @@ -976,6 +987,11 @@ export function render({ navigate, route }) { } }; + const syncComposerLayout = () => { + const height = Number.parseFloat(input?.style.height || '0'); + form.classList.toggle('dm-chat-input--multiline', height > 52); + }; + const rememberEmojiSelection = () => { if (!input) return; const valueLength = String(input.value || '').length; @@ -1013,6 +1029,7 @@ export function render({ navigate, route }) { } } autoResizeComposer(input); + syncComposerLayout(); }; if (emojiSlot) { @@ -1053,6 +1070,7 @@ export function render({ navigate, route }) { if (restoreDraft && input) { input.value = draftToRestore || ''; autoResizeComposer(input); + syncComposerLayout(); focusInputToEnd(); } }; @@ -1063,6 +1081,7 @@ export function render({ navigate, route }) { syncEditBanner(); if (restoreDraft && input) { autoResizeComposer(input); + syncComposerLayout(); focusInputToEnd(); } }; @@ -1085,6 +1104,7 @@ export function render({ navigate, route }) { if (input) { input.value = String(parsed?.visibleText || ''); autoResizeComposer(input); + syncComposerLayout(); focusInputToEnd(); } }; @@ -1246,6 +1266,7 @@ export function render({ navigate, route }) { if (input) { input.value = text; autoResizeComposer(input); + syncComposerLayout(); focusInputToEnd(); } addChatMessage(chatId, `${activeEdit ? 'Ошибка изменения' : 'Ошибка отправки'}: ${e.message || 'unknown'}`); @@ -1358,9 +1379,11 @@ export function render({ navigate, route }) { }); autoResizeComposer(input); + syncComposerLayout(); input?.addEventListener('input', () => { rememberEmojiSelection(); autoResizeComposer(input); + syncComposerLayout(); }); input?.addEventListener('select', rememberEmojiSelection); input?.addEventListener('keyup', rememberEmojiSelection); @@ -1403,26 +1426,12 @@ export function render({ navigate, route }) { if (!text) return; input.value = ''; autoResizeComposer(input); + syncComposerLayout(); closeEmojiPicker(); await sendTextMessage(text); focusInputToEnd(); }); - form.querySelector('#chat-voice-input')?.addEventListener('click', async () => { - await openSpeechInputModal({ - navigate, - onTextReady: (text) => { - const prev = String(input.value || '').trim(); - input.value = prev ? `${prev} ${text}` : text; - autoResizeComposer(input); - }, - onSendText: async (text) => sendTextMessage(text), - onSendQueued: () => { - showToast('Сообщение будет отправлено автоматически после распознавания', { timeoutMs: 1000 }); - }, - }); - }); - form.querySelector('.dm-send-btn')?.addEventListener('pointerdown', (event) => { event.preventDefault(); try { @@ -1438,6 +1447,7 @@ export function render({ navigate, route }) { if (!text) return; input.value = ''; autoResizeComposer(input); + syncComposerLayout(); closeEmojiPicker(); await sendTextMessage(text); focusInputToEnd(); @@ -1464,13 +1474,6 @@ export function render({ navigate, route }) { markAsRead: false, scrollMode: hasUnreadIncoming ? 'unread' : 'latest', }); - if (hasUnreadIncoming) { - window.requestAnimationFrame(() => scrollToUnreadSeparator(log)); - window.setTimeout(() => scrollToUnreadSeparator(log), 180); - } else { - window.requestAnimationFrame(() => scrollToLatestMessage(log)); - window.setTimeout(() => scrollToLatestMessage(log), 180); - } window.setTimeout(() => { if (markChatRead(chatId) > 0) { notifyUnreadStateUpdated(); @@ -1480,7 +1483,7 @@ export function render({ navigate, route }) { window.requestAnimationFrame(() => { boundScrollContainer = log.closest('.screen-content') || wrap.parentElement || wrap; boundScrollContainer?.addEventListener('scroll', handleHistoryScroll, { passive: true }); - void loadHistoryPage({ preserveScroll: false }); + void loadHistoryPage({ preserveScroll: true }); }); screen.cleanup = () => { setChatKeyboardOpen(false); diff --git a/shine-UI/js/pages/settings-view.js b/shine-UI/js/pages/settings-view.js index 05a47605..6630a53a 100644 --- a/shine-UI/js/pages/settings-view.js +++ b/shine-UI/js/pages/settings-view.js @@ -1,6 +1,5 @@ import { renderHeader } from '../components/header.js'; import { addAppLogEntry, authService, closeCurrentSessionAndSignOut, state } from '../state.js'; -import { makeProfileLinksRoute } from '../services/shine-routes.js'; export const pageMeta = { id: 'settings-view', title: 'Настройки' }; @@ -41,7 +40,6 @@ export function render({ navigate }) { const card = document.createElement('div'); card.className = 'card stack'; card.innerHTML = ` - @@ -50,9 +48,6 @@ export function render({ navigate }) { `; - card.querySelector('#settings-links').addEventListener('click', () => { - navigate(makeProfileLinksRoute(state.session.login)); - }); card.querySelector('#settings-device').addEventListener('click', () => navigate('device-view')); card.querySelector('#settings-remote-addblock').addEventListener('click', () => navigate('remote-addblock-session-view')); card.querySelector('#settings-servers').addEventListener('click', () => navigate('server-settings-view')); diff --git a/shine-UI/styles/components.css b/shine-UI/styles/components.css index 30b0bacd..ef597ae9 100644 --- a/shine-UI/styles/components.css +++ b/shine-UI/styles/components.css @@ -59,6 +59,17 @@ border-radius: 12px; } +.header-icon-svg { + display: block; + width: 1em; + height: 1em; +} + +.header-icon-svg--phone { + width: 22px; + height: 22px; +} + .chat-header-menu-btn { border-radius: 12px; font-size: 24px; @@ -4630,6 +4641,7 @@ html, body { overflow-x: hidden; } .screen-content:has(> .dm-chat-screen) { padding-top: 0; + padding-bottom: 0; scrollbar-width: thin; scrollbar-color: rgba(212, 175, 55, 0.65) rgba(255, 255, 255, 0.06); } @@ -4685,12 +4697,13 @@ html, body { overflow-x: hidden; } .dm-chat-input { gap: 10px; - grid-template-columns: 1fr auto; + grid-template-columns: minmax(0, 1fr) auto; align-items: end; position: sticky; bottom: 0; z-index: 10; - padding: 10px; + margin-inline: -14px; + padding: 10px 12px calc(10px + env(safe-area-inset-bottom)); border-top: 1px solid rgba(212, 175, 55, 0.22); background: rgba(8, 12, 20, 0.9); backdrop-filter: blur(12px); @@ -4733,18 +4746,35 @@ html, body { overflow-x: hidden; } .dm-actions-col { display: grid; - grid-template-columns: repeat(2, 42px); - grid-template-rows: 42px; + grid-template-columns: repeat(2, 46px); + grid-template-rows: 46px; grid-template-areas: "emoji send"; gap: 6px; + align-self: end; +} + +.dm-chat-input--multiline .dm-actions-col { + grid-template-columns: 46px; + grid-template-rows: repeat(2, 46px); + grid-template-areas: + "emoji" + "send"; } .dm-emoji-btn { grid-area: emoji; - min-width: 42px; - width: 42px; + min-width: 46px; + width: 46px; padding: 0; - font-size: 20px; + line-height: 1; +} + +.dm-emoji-btn__glyph { + display: inline-grid; + place-items: center; + width: 100%; + height: 100%; + font-size: 27px; line-height: 1; } @@ -4918,6 +4948,8 @@ html, body { overflow-x: hidden; } .dm-chat-input .dm-input { min-height: 42px; max-height: 180px; + width: 100%; + min-width: 0; resize: none; overflow-y: auto; scrollbar-width: none; @@ -4926,6 +4958,10 @@ html, body { overflow-x: hidden; } padding: 10px 12px; } +.dm-chat-input--multiline .dm-input { + padding-right: 14px; +} + .dm-chat-input .dm-input::-webkit-scrollbar { width: 0; height: 0; @@ -4970,8 +5006,8 @@ html, body { overflow-x: hidden; } .dm-send-icon-btn { grid-area: send; - min-width: 42px; - width: 42px; + min-width: 46px; + width: 46px; padding: 0; font-size: 15px; line-height: 1; @@ -5617,8 +5653,8 @@ html, body { overflow-x: hidden; } } .profile-top-icon-btn img { - width: 44px; - height: 44px; + width: 48px; + height: 48px; display: block; opacity: 0.94; filter: drop-shadow(0 1px 4px rgba(0, 0, 0, 0.34));