UI: переход на history-router без # и короткие ссылки тредов
This commit is contained in:
parent
3a0899bcfe
commit
db2d9a666b
@ -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`
|
||||
@ -1,2 +1,2 @@
|
||||
client.version=1.2.65
|
||||
server.version=1.2.59
|
||||
client.version=1.2.66
|
||||
server.version=1.2.60
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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('Для этого действия нужно войти');
|
||||
}
|
||||
|
||||
@ -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('Для этого действия нужно войти');
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
@ -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) {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user