SHA256
Добавил гостевой режим, единые shine-ссылки и пометку о нестабильности мнений
This commit is contained in:
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user