From db2d9a666b949cbde17d889b2d5c43ce263209b7247fe4bcdc4cf329bf007b00 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Tue, 19 May 2026 10:15:15 +0300 Subject: [PATCH] =?UTF-8?q?UI:=20=D0=BF=D0=B5=D1=80=D0=B5=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=20=D0=BD=D0=B0=20history-router=20=D0=B1=D0=B5=D0=B7=20#?= =?UTF-8?q?=20=D0=B8=20=D0=BA=D0=BE=D1=80=D0=BE=D1=82=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D0=B8=20=D1=82=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...2026-05-19_1012_history-router-без-hash.md | 21 ++++++++++++++ VERSION.properties | 4 +-- shine-UI/js/app.js | 9 +++--- shine-UI/js/pages/channel-thread-view.js | 8 +++--- shine-UI/js/pages/channel-view.js | 6 ++-- shine-UI/js/pages/channels-list.js | 6 ++-- shine-UI/js/pages/developer-settings-view.js | 3 +- shine-UI/js/pages/registration-keys-view.js | 4 +-- shine-UI/js/router.js | 28 +++++++++++++------ 9 files changed, 61 insertions(+), 28 deletions(-) create mode 100644 Dev_Docs/Pending_Features/2026-05-19_1012_history-router-без-hash.md diff --git a/Dev_Docs/Pending_Features/2026-05-19_1012_history-router-без-hash.md b/Dev_Docs/Pending_Features/2026-05-19_1012_history-router-без-hash.md new file mode 100644 index 0000000..10c9836 --- /dev/null +++ b/Dev_Docs/Pending_Features/2026-05-19_1012_history-router-без-hash.md @@ -0,0 +1,21 @@ +# Переход на history-router без `#` в URL + +- Краткое описание: + - UI переведён с hash-router на history API роутинг. + - Ссылки на треды переведены в формат без hash сообщения: `/m/{blockchainName}/{blockNumber}`. + - Навигация и шаринг-ссылки обновлены под `pathname`. + +- Что проверять: + - Открытие UI с корня (`/`) и переход на стартовую страницу без тёмного экрана. + - Навигация между основными экранами (сообщения, каналы, профиль, настройки). + - Переход в канал, открытие треда, ответ/лайк, шаринг ссылки. + - Прямое открытие URL формата `/m/{blockchain}/{number}`. + - Поведение после refresh (F5) при настроенном серверном fallback на `index.html`. + +- Ожидаемый результат: + - Приложение работает без `#` в адресе. + - Треды открываются и действия по сообщению (reply/like/share) работают корректно. + - Нет зависания на пустом/тёмном экране при входе. + +- Статус: + - `pending` diff --git a/VERSION.properties b/VERSION.properties index 773849c..e1f1883 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.65 -server.version=1.2.59 +client.version=1.2.66 +server.version=1.2.60 diff --git a/shine-UI/js/app.js b/shine-UI/js/app.js index 225b149..fbc6dd3 100644 --- a/shine-UI/js/app.js +++ b/shine-UI/js/app.js @@ -289,7 +289,7 @@ function consumeCallPushActionFromUrlIfAny() { params.delete('callPushAction'); params.delete('callPushPayload'); const nextQuery = params.toString(); - const nextUrl = `${window.location.pathname}${nextQuery ? `?${nextQuery}` : ''}${window.location.hash || ''}`; + const nextUrl = `${window.location.pathname}${nextQuery ? `?${nextQuery}` : ''}`; window.history.replaceState({}, '', nextUrl); } catch { // ignore URL parsing errors @@ -634,7 +634,7 @@ function renderPageFailureFallback(pageId, error) { stack: error?.stack || '', context: { pageId, - routeHash: window.location.hash || '', + routeHash: window.location.pathname || '', }, }); @@ -1031,13 +1031,14 @@ async function init() { startPeriodicUiVersionCheck(); await ensureSessionRuntimeStarted(); - if (!window.location.hash) { + if (!window.location.pathname || window.location.pathname === '/' || window.location.pathname === '/index.html') { navigate(state.session.isAuthorized ? 'messages-list' : 'start-view'); + renderApp(); } else { renderApp(); } - window.addEventListener('hashchange', renderApp); + window.addEventListener('popstate', renderApp); document.addEventListener('visibilitychange', () => { if (document.visibilityState !== 'visible') return; void checkConnectionHealth(); diff --git a/shine-UI/js/pages/channel-thread-view.js b/shine-UI/js/pages/channel-thread-view.js index 6840bec..685ec82 100644 --- a/shine-UI/js/pages/channel-thread-view.js +++ b/shine-UI/js/pages/channel-thread-view.js @@ -81,7 +81,8 @@ function messageRefKey(messageRef) { function buildAbsoluteRouteUrl(routePath = '') { const cleanRoute = String(routePath || '').replace(/^#?\/?/, ''); const url = new URL(window.location.href); - url.hash = `#/${cleanRoute}`; + url.pathname = `/${cleanRoute}`; + url.hash = ''; return url.toString(); } @@ -213,7 +214,6 @@ function buildThreadRouteFromTarget(target, selector) { 'm', encodeRoutePart(target.blockchainName), target.blockNumber, - normalizeRouteHash(target.blockHash), ].join('/'); } @@ -525,7 +525,7 @@ export function render({ navigate, route }) { const next = render({ navigate, route }); current.replaceWith(next); } catch (error) { - logThreadRuntimeError('rerender', error, { routeHash: window.location.hash }); + logThreadRuntimeError('rerender', error, { routePath: window.location.pathname }); } }; @@ -543,7 +543,7 @@ export function render({ navigate, route }) { const login = state.session.login; const storagePwd = state.session.storagePwdInMemory; if (!login || !storagePwd) { - state.authReturnHash = window.location.hash || '#/channels-list'; + state.authReturnHash = window.location.pathname || '/channels-list'; navigate('login-view'); throw new Error('Для этого действия нужно войти'); } diff --git a/shine-UI/js/pages/channel-view.js b/shine-UI/js/pages/channel-view.js index 21c7b1e..7cae989 100644 --- a/shine-UI/js/pages/channel-view.js +++ b/shine-UI/js/pages/channel-view.js @@ -110,7 +110,8 @@ function blockRefToMessageKey(blockRef, fallbackBch = '') { function buildAbsoluteRouteUrl(routePath = '') { const cleanRoute = String(routePath || '').replace(/^#?\/?/, ''); const url = new URL(window.location.href); - url.hash = `#/${cleanRoute}`; + url.pathname = `/${cleanRoute}`; + url.hash = ''; return url.toString(); } @@ -150,7 +151,6 @@ function buildThreadRoute(messageRef, selector) { 'm', encodeRoutePart(messageRef.blockchainName), messageRef.blockNumber, - normalizeRouteHash(messageRef.blockHash), ].join('/'); } @@ -890,7 +890,7 @@ export function render({ navigate, route }) { const login = state.session.login; const storagePwd = state.session.storagePwdInMemory; if (!login || !storagePwd) { - state.authReturnHash = window.location.hash || '#/channels-list'; + state.authReturnHash = window.location.pathname || '/channels-list'; navigate('login-view'); throw new Error('Для этого действия нужно войти'); } diff --git a/shine-UI/js/pages/channels-list.js b/shine-UI/js/pages/channels-list.js index f77916d..20fa449 100644 --- a/shine-UI/js/pages/channels-list.js +++ b/shine-UI/js/pages/channels-list.js @@ -1167,9 +1167,9 @@ export function render({ navigate, route }) { const rerenderList = () => { try { - const expectedHash = `#/channels-list/${listState.activeTab}`; - if (window.location.hash !== expectedHash) { - window.history.replaceState({}, '', expectedHash); + const expectedPath = `/channels-list/${listState.activeTab}`; + if (window.location.pathname !== expectedPath) { + window.history.replaceState({}, '', expectedPath); } } catch { // ignore history errors diff --git a/shine-UI/js/pages/developer-settings-view.js b/shine-UI/js/pages/developer-settings-view.js index 1a7c8c2..7ea90ef 100644 --- a/shine-UI/js/pages/developer-settings-view.js +++ b/shine-UI/js/pages/developer-settings-view.js @@ -184,7 +184,8 @@ function openDeveloperAvatarUploadModal({ walletLogin, storagePwd, gateway } = { async function forceUiUpdateNow() { try { - window.location.hash = '#/settings-view'; + window.history.replaceState({}, '', '/settings-view'); + window.dispatchEvent(new PopStateEvent('popstate')); } catch {} if (!('serviceWorker' in navigator)) { window.location.reload(); diff --git a/shine-UI/js/pages/registration-keys-view.js b/shine-UI/js/pages/registration-keys-view.js index c24a5e8..22feaac 100644 --- a/shine-UI/js/pages/registration-keys-view.js +++ b/shine-UI/js/pages/registration-keys-view.js @@ -132,8 +132,8 @@ export function render({ navigate }) { : `Ключи сохранены. Регистрация завершена для @${state.registrationDraft.login}. Далее откройте вкладку «Каналы».`); const nextHash = String(state.authReturnHash || '').trim(); state.authReturnHash = ''; - if (nextHash.startsWith('#/')) { - navigate(nextHash.slice(2)); + if (nextHash.startsWith('/')) { + navigate(nextHash.slice(1)); } else { navigate('profile-view'); } diff --git a/shine-UI/js/router.js b/shine-UI/js/router.js index d457dcd..71b7962 100644 --- a/shine-UI/js/router.js +++ b/shine-UI/js/router.js @@ -14,7 +14,12 @@ export const PRE_AUTH_PAGES = [ ]; export function getRoute() { - const raw = window.location.hash.replace(/^#\/?/, ''); + const currentPath = String(window.location.pathname || '').trim(); + const raw = currentPath + .replace(/^\/+/, '') + .replace(/^index\.html$/i, '') + .replace(/^index\.html\//i, '') + .replace(/\/+$/, ''); if (!raw) { return { pageId: '', params: {} }; } @@ -52,8 +57,8 @@ export function getRoute() { if (pageId === 'channel') { // Короткий формат: - // #/channel/{ownerBlockchainName}/{channelName} - // #/channel/{ownerBlockchainName}/{channelName}/{messageBlockNumber} + // /channel/{ownerBlockchainName}/{channelName} + // /channel/{ownerBlockchainName}/{channelName}/{messageBlockNumber} const ownerBlockchainName = decodePart(segments[1] || ''); const channelName = decodePart(segments[2] || ''); const messageBlockNumber = segments[3] || ''; @@ -91,10 +96,10 @@ export function getRoute() { params: { messageBlockchainName: decodePart(segments[1]), messageBlockNumber: segments[2] || '', - messageBlockHash: segments[3] || '', - channelOwnerBlockchainName: decodePart(segments[4]), - channelRootBlockNumber: segments[5] || '', - channelRootBlockHash: segments[6] || '', + messageBlockHash: '', + channelOwnerBlockchainName: decodePart(segments[3]), + channelRootBlockNumber: segments[4] || '', + channelRootBlockHash: segments[5] || '', }, }; } @@ -105,7 +110,7 @@ export function getRoute() { params: { messageBlockchainName: decodePart(segments[1]), messageBlockNumber: segments[2] || '', - messageBlockHash: segments[3] || '', + messageBlockHash: '', channelOwnerBlockchainName: '', channelRootBlockNumber: '', channelRootBlockHash: '', @@ -149,7 +154,12 @@ export function getRoute() { } export function navigate(path) { - window.location.hash = `#/${path}`; + const cleanPath = String(path || '').replace(/^\/+/, ''); + const nextPath = cleanPath ? `/${cleanPath}` : '/'; + if (window.location.pathname !== nextPath) { + window.history.pushState({}, '', nextPath); + } + window.dispatchEvent(new PopStateEvent('popstate')); } export function resolveToolbarActive(pageId) {