Добавил гостевой режим, единые shine-ссылки и пометку о нестабильности мнений

This commit is contained in:
AidarKC
2026-05-20 16:14:59 +03:00
parent aa35d87885
commit 21413268f3
46 changed files with 1125 additions and 310 deletions
+40 -32
View File
@@ -2,6 +2,8 @@ import { renderHeader } from '../components/header.js';
import { authService, state } from '../state.js';
import { renderUserAvatar } from '../components/avatar-image.js';
import { loadUserProfileCard } from '../services/user-connections.js';
import { makeProfileRoute } from '../services/shine-routes.js';
import { makeProfileLinksRoute } from '../services/shine-routes.js';
export const pageMeta = { id: 'network-view', title: 'Связи' };
@@ -14,6 +16,14 @@ function normalizeLogin(value) {
return String(value || '').trim();
}
function createDebounced(fn, delayMs = 2000) {
let timer = 0;
return (...args) => {
if (timer) window.clearTimeout(timer);
timer = window.setTimeout(() => fn(...args), delayMs);
};
}
function normKey(value) {
return normalizeLogin(value).toLowerCase();
}
@@ -507,6 +517,7 @@ let persistedCenterHistory = [];
export function render({ navigate, route }) {
const keepHistory = String(route?.params?.mode || '').trim().toLowerCase() === 'keep-history';
const routeLogin = normalizeLogin(route?.params?.login || '');
if (!keepHistory) {
persistedCenterLogin = '';
persistedCenterHistory = [];
@@ -533,7 +544,7 @@ export function render({ navigate, route }) {
const cleanLogin = normalizeLogin(login);
if (!cleanLogin) return '';
if (normKey(cleanLogin) === normKey(state.session.login)) return 'profile-view';
return `user/${encodeURIComponent(cleanLogin)}/${encodeURIComponent('network-view/keep-history')}`;
return makeProfileRoute(cleanLogin);
}
function helpText() {
@@ -551,6 +562,15 @@ export function render({ navigate, route }) {
persistedCenterHistory = [...centerHistory];
}
function syncLinksUrl(login, { push = false } = {}) {
const clean = normalizeLogin(login);
if (!clean) return;
const nextPath = `/${makeProfileLinksRoute(clean)}`;
if (window.location.pathname === nextPath) return;
if (push) window.history.pushState({}, '', nextPath);
else window.history.replaceState({}, '', nextPath);
}
function setBackButtonState(backBtn) {
if (!(backBtn instanceof HTMLButtonElement)) return;
backBtn.disabled = centerHistory.length === 0;
@@ -568,13 +588,8 @@ export function render({ navigate, route }) {
<input class="input" id="network-search-input" type="text" maxlength="30" placeholder="Введите логин" />
<button class="primary-btn" type="button" id="network-search-run">Искать</button>
</div>
<div class="meta-muted" id="network-search-meta">Введите логин и нажмите «Искать».</div>
<div class="meta-muted" id="network-search-meta">Введите логин. Поиск начнётся автоматически через 2 секунды.</div>
<div class="stack" id="network-search-results"></div>
<div class="form-actions-grid">
<button class="ghost-btn" type="button" id="network-search-profile" disabled>Показать профиль</button>
<button class="primary-btn" type="button" id="network-search-graph" disabled>Показать связи</button>
</div>
<button class="secondary-btn" type="button" id="network-search-ok" disabled>OK</button>
</div>
</div>
`;
@@ -585,9 +600,6 @@ export function render({ navigate, route }) {
const runBtn = root.querySelector('#network-search-run');
const metaEl = root.querySelector('#network-search-meta');
const resultsEl = root.querySelector('#network-search-results');
const profileBtn = root.querySelector('#network-search-profile');
const graphBtn = root.querySelector('#network-search-graph');
const okBtn = root.querySelector('#network-search-ok');
if (!(modal instanceof HTMLElement) || !(inputEl instanceof HTMLInputElement) || !(resultsEl instanceof HTMLElement)) {
root.innerHTML = '';
return;
@@ -607,10 +619,6 @@ export function render({ navigate, route }) {
if (!(row instanceof HTMLElement)) return;
row.classList.toggle('is-selected', String(row.dataset.candidate || '') === selectedLogin);
});
const hasSelected = Boolean(selectedLogin);
if (profileBtn instanceof HTMLButtonElement) profileBtn.disabled = !hasSelected;
if (graphBtn instanceof HTMLButtonElement) graphBtn.disabled = !hasSelected;
if (okBtn instanceof HTMLButtonElement) okBtn.disabled = !hasSelected;
};
const renderCandidates = (logins) => {
@@ -661,6 +669,8 @@ export function render({ navigate, route }) {
});
closeBtn?.addEventListener('click', close);
runBtn?.addEventListener('click', () => { void runSearch(); });
const debouncedSearch = createDebounced(() => { void runSearch(); }, 2000);
inputEl.addEventListener('input', debouncedSearch);
inputEl.addEventListener('keydown', (event) => {
if (event.key === 'Enter') {
event.preventDefault();
@@ -672,23 +682,11 @@ export function render({ navigate, route }) {
if (!(target instanceof HTMLElement)) return;
const button = target.closest('[data-candidate]');
if (!(button instanceof HTMLElement)) return;
applySelection(String(button.dataset.candidate || ''));
});
profileBtn?.addEventListener('click', () => {
if (!selectedLogin) return;
const routeTo = profileInfoRoute(selectedLogin);
if (!routeTo) return;
const nextLogin = String(button.dataset.candidate || '');
applySelection(nextLogin);
if (!nextLogin) return;
close();
navigate(routeTo);
});
graphBtn?.addEventListener('click', () => {
if (!selectedLogin) return;
close();
void load(selectedLogin, { pushHistory: true });
});
okBtn?.addEventListener('click', () => {
if (!selectedLogin) return;
metaEl.textContent = `Выбран пользователь «${selectedLogin}». Используйте кнопки ниже.`;
void load(nextLogin, { pushHistory: true });
});
window.setTimeout(() => inputEl.focus(), 0);
@@ -765,6 +763,7 @@ export function render({ navigate, route }) {
if (pushHistory && prevCenter && normKey(prevCenter) !== normKey(targetCenter)) {
centerHistory.push(prevCenter);
}
syncLinksUrl(targetCenter, { push: pushHistory });
const model = buildGraphModel(graph, targetCenter);
const layout = layoutNodes(model);
@@ -839,13 +838,22 @@ export function render({ navigate, route }) {
appScreenEl?.classList.remove('network-scroll-lock');
};
if (keepHistory && centerLogin) {
if (routeLogin) {
centerLogin = routeLogin;
centerHistory = [];
persistHistory();
void load(centerLogin, { pushHistory: false });
} else if (keepHistory && centerLogin) {
void load(centerLogin, { pushHistory: false });
} else {
centerLogin = normalizeLogin(state.session.login || '');
centerHistory = [];
persistHistory();
void load(centerLogin, { pushHistory: false });
if (centerLogin) {
void load(centerLogin, { pushHistory: false });
} else {
window.setTimeout(() => openSearchModal(), 0);
}
}
setBackButtonState(backBtnEl);