|
|
|
@@ -5,7 +5,7 @@
|
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
|
|
|
<title>Оценка поисковой выдачи · Акылдаш</title>
|
|
|
|
|
<style>
|
|
|
|
|
:root { color-scheme: light; font: 16px/1.5 "Noto Sans", "Segoe UI", sans-serif; color: #38262A; background: #F7F2E8; --ink: #38262A; --muted: #786469; --line: #DED3CE; --surface: #fff; --surface-muted: #FBF8F4; --brand: #9D2633; --brand-dark: #84202B; --accent: #A37B32; --focus: #245C78; --subtle: #F8EBED; --serif: "Noto Serif", Georgia, serif; }
|
|
|
|
|
:root { color-scheme: light; font: 16px/1.5 "Noto Sans", sans-serif; color: #38262A; background: #F7F2E8; --ink: #38262A; --muted: #786469; --line: #DED3CE; --surface: #fff; --surface-muted: #FBF8F4; --brand: #9D2633; --brand-dark: #84202B; --accent: #A37B32; --focus: #245C78; --subtle: #F8EBED; --serif: "Noto Serif", serif; }
|
|
|
|
|
* { box-sizing: border-box; }
|
|
|
|
|
body { margin: 0; color: var(--ink); background: #F7F2E8; }
|
|
|
|
|
.review__skip { position: absolute; inset-block-start: 0; inset-inline-start: -10000px; padding: .5rem 1rem; background: var(--surface); }
|
|
|
|
@@ -53,14 +53,14 @@
|
|
|
|
|
.review__comment { min-height: 4rem; resize: vertical; background: var(--surface); }
|
|
|
|
|
.review__document { position: sticky; inset-block-start: 1rem; min-height: 24rem; }
|
|
|
|
|
.review__document-meta { margin-block-end: 1rem; padding-block-end: 1rem; border-block-end: 1px solid var(--line); color: var(--muted); }
|
|
|
|
|
.review__document-body { max-width: 75ch; overflow-wrap: anywhere; font: 400 1.125rem/1.65 "Noto Sans", "Segoe UI", sans-serif; }
|
|
|
|
|
.review__document-body { max-width: 75ch; overflow-wrap: anywhere; font: 400 1.125rem/1.65 "Noto Sans", sans-serif; }
|
|
|
|
|
.review__document-body img { max-width: 100%; height: auto; }
|
|
|
|
|
.review__actions { max-width: 1320px; margin: 1.5rem auto 0; padding-inline: clamp(1rem, 3vw, 3rem); }
|
|
|
|
|
.review__actions-inner { display: flex; align-items: end; gap: .75rem; padding: 1rem; background: var(--surface); border: 1px solid var(--line); border-radius: 6px; }
|
|
|
|
|
.review__actions label { display: grid; gap: .35rem; flex: 1; color: var(--muted); font-size: .875rem; font-weight: 700; }
|
|
|
|
|
.review__actions label:last-of-type { flex: 2; }
|
|
|
|
|
dialog { max-width: min(38rem, calc(100% - 2rem)); max-height: calc(100% - 2rem); border: 1px solid var(--line); border-radius: 6px; padding: 1.5rem; box-shadow: 0 1rem 2.5rem rgb(10 20 30 / .18); }
|
|
|
|
|
dialog::backdrop { background: rgb(10 20 30 / .5); }
|
|
|
|
|
dialog { max-width: min(38rem, calc(100% - 2rem)); max-height: calc(100% - 2rem); border: 1px solid var(--line); border-radius: 6px; padding: 1.5rem; box-shadow: 0 1rem 2.5rem rgb(56 38 42 / .18); }
|
|
|
|
|
dialog::backdrop { background: rgb(56 38 42 / .5); }
|
|
|
|
|
.review__dialog-close { float: inline-end; }
|
|
|
|
|
.review__empty { padding: 3rem 1rem; color: var(--muted); text-align: center; }
|
|
|
|
|
.review__empty strong { display: block; margin-block-end: .35rem; color: var(--ink); }
|
|
|
|
@@ -71,9 +71,9 @@
|
|
|
|
|
.review__tour h2 { margin: 0 0 .65rem; font-size: clamp(1.5rem, 3vw, 2rem); line-height: 1.2; letter-spacing: -.02em; }
|
|
|
|
|
.review__tour p { margin: 0; color: var(--muted); }
|
|
|
|
|
.review__tour-footer { display: flex; justify-content: space-between; gap: .75rem; align-items: center; margin-block-start: 1.5rem; }
|
|
|
|
|
.review__tour-progress { color: var(--muted); font-size: .8125rem; }
|
|
|
|
|
.review__tour-progress { color: var(--muted); font-size: .875rem; }
|
|
|
|
|
.review__tour-actions { display: flex; gap: .5rem; }
|
|
|
|
|
.review__nav { display: flex; align-items: center; justify-content: space-between; gap: 1rem; max-width: 1440px; margin: auto; padding: .75rem clamp(1rem, 3vw, 3rem); border-block-end: 1px solid var(--line); }
|
|
|
|
|
.review__nav { display: flex; align-items: center; justify-content: space-between; gap: 1rem; max-width: 1320px; margin: auto; padding: .75rem clamp(1rem, 3vw, 3rem); border-block-end: 1px solid var(--line); }
|
|
|
|
|
.review__nav-links, .review__nav-account { display: flex; align-items: center; gap: .5rem; }
|
|
|
|
|
.review__nav button, .review__nav a { min-height: 44px; }
|
|
|
|
|
.review__nav .is-active { color: var(--brand); border-color: var(--brand); background: var(--subtle); }
|
|
|
|
@@ -89,13 +89,13 @@
|
|
|
|
|
.review__user-reset label { display: grid; gap: .25rem; margin: 0; }
|
|
|
|
|
.review__user-reset input { width: min(15rem, 100%); }
|
|
|
|
|
.review__header-top { padding-block: .75rem; align-items: center; }
|
|
|
|
|
.review__header-content { max-width: 1440px; margin: auto; padding-inline: clamp(1rem, 3vw, 3rem); }
|
|
|
|
|
.review__header-content { max-width: 1320px; margin: auto; padding-inline: clamp(1rem, 3vw, 3rem); }
|
|
|
|
|
body.review__locked .review__header, body.review__locked .review__main, body.review__locked .review__actions, body.review__locked footer, body.review__locked .search { visibility: hidden; }
|
|
|
|
|
.search { max-width: 1600px; margin: auto; }
|
|
|
|
|
.search { max-width: 1320px; margin: auto; }
|
|
|
|
|
.search__query { padding: 1rem clamp(1rem, 3vw, 3rem); background: var(--surface); border-block-end: 1px solid var(--line); }
|
|
|
|
|
.search__query > label, .search__filters > label { display: grid; gap: .35rem; color: var(--muted); font-size: .85rem; font-weight: 600; }
|
|
|
|
|
.search__query-row { display: grid; grid-template-columns: minmax(15rem, 1fr) auto 9rem; gap: .75rem; align-items: end; }
|
|
|
|
|
.search__language { display: grid; gap: .25rem; color: var(--muted); font-size: .8125rem; }
|
|
|
|
|
.search__language { display: grid; gap: .25rem; color: var(--muted); font-size: .875rem; }
|
|
|
|
|
.search__status { margin-inline: clamp(1rem, 3vw, 3rem); }
|
|
|
|
|
.search__layout { display: grid; grid-template-columns: 230px minmax(320px, .9fr) minmax(420px, 1.2fr); min-height: 65vh; border-block: 1px solid var(--line); background: var(--surface); }
|
|
|
|
|
.search__filters, .search__results, .search__document { min-width: 0; padding: 1rem; }
|
|
|
|
@@ -115,11 +115,14 @@
|
|
|
|
|
.search__document { min-height: 60vh; }
|
|
|
|
|
.search__document-controls { display: flex; flex-wrap: wrap; gap: .75rem; }
|
|
|
|
|
.search__document-controls label { display: grid; gap: .25rem; min-width: 11rem; color: var(--muted); font-size: .875rem; }
|
|
|
|
|
.search__document-body { max-width: 75ch; margin: auto; font: 400 1.125rem/1.65 "Noto Sans", "Segoe UI", sans-serif; }
|
|
|
|
|
.search__document-body { max-width: 75ch; margin: auto; font: 400 1.125rem/1.65 "Noto Sans", sans-serif; }
|
|
|
|
|
.search__document-body h1, .search__document-body h2, .search__document-body h3 { font-family: var(--serif); }
|
|
|
|
|
.search__document-body img { max-width: 100%; height: auto; }
|
|
|
|
|
.search__sr-only { position: absolute; inline-size: 1px; block-size: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
|
|
|
|
|
@media (max-width: 800px) {
|
|
|
|
|
.review__nav { align-items: start; flex-wrap: wrap; }
|
|
|
|
|
.review__nav-links { order: 3; flex: 1 1 100%; flex-wrap: wrap; }
|
|
|
|
|
.review__nav-account { margin-inline-start: auto; }
|
|
|
|
|
.search__layout { grid-template-columns: 1fr; }
|
|
|
|
|
.search__filters, .search__results { border-inline-end: 0; border-block-end: 1px solid var(--line); }
|
|
|
|
|
.search__document { min-height: 24rem; }
|
|
|
|
@@ -135,7 +138,7 @@
|
|
|
|
|
.review__actions label { flex: 1 1 14rem !important; }
|
|
|
|
|
.review__actions button { flex: 1 1 100%; }
|
|
|
|
|
}
|
|
|
|
|
@media (max-width: 480px) { .review__search { grid-template-columns: 1fr; } .review__search .review__field:first-child, .review__search button { grid-column: auto; } .review__rating { grid-template-columns: repeat(2, minmax(0, 1fr)); } .search__query-row { grid-template-columns: 1fr 1fr; } .search__query-row > input { grid-column: 1 / -1; } .review__nav { align-items: start; flex-wrap: wrap; } .review__nav-links { order: 3; width: 100%; flex-wrap: wrap; } .review__nav-account { margin-inline-start: auto; } }
|
|
|
|
|
@media (max-width: 480px) { .review__search { grid-template-columns: 1fr; } .review__search .review__field:first-child, .review__search button { grid-column: auto; } .review__rating { grid-template-columns: repeat(2, minmax(0, 1fr)); } .search__query-row { grid-template-columns: 1fr 1fr; } .search__query-row > input { grid-column: 1 / -1; } }
|
|
|
|
|
@media (hover: hover) { button:hover { background: var(--surface-muted); border-color: var(--muted); } .review__button--primary:hover { background: var(--brand-dark); border-color: var(--brand-dark); } .review__result:hover { background: var(--subtle); } .review__result-title:hover, .search__result-title:hover { background: none; color: var(--brand); } .review__rating label:hover { border-color: var(--brand); } }
|
|
|
|
|
@media (prefers-reduced-motion: reduce) { *, *::before, *::after { scroll-behavior: auto !important; transition: none !important; } }
|
|
|
|
|
</style>
|
|
|
|
@@ -176,7 +179,7 @@
|
|
|
|
|
<dialog id="users-dialog" aria-labelledby="users-heading"><section class="review__page"><h2 id="users-heading">Управление пользователями</h2><form id="create-user-form"><label>Логин<input id="new-username" autocomplete="off" minlength="3" maxlength="64" required></label><label>Временный пароль<input id="temporary-password" type="password" autocomplete="new-password" minlength="12" maxlength="256" required></label><p>Пароль — от 12 символов. Новый пользователь сменит его при первом входе. Передайте временный пароль отдельно.</p><p id="users-error" role="alert" class="review__status review__status--error"></p><button class="review__button--primary" type="submit">Создать пользователя</button></form><ul id="user-list" class="review__user-list"></ul><button type="button" data-close="users-dialog">Закрыть</button></section></dialog>
|
|
|
|
|
<dialog id="document-dialog"><button class="review__dialog-close" id="dialog-close" type="button">Закрыть</button><h2 id="dialog-heading">Документ</h2><div class="review__document-body" id="dialog-body"></div></dialog>
|
|
|
|
|
<div class="review__tour-spotlight" id="tutorial-spotlight" hidden aria-hidden="true"></div><dialog class="review__tour" id="tutorial-dialog" aria-labelledby="tutorial-heading" aria-describedby="tutorial-text"><div class="review__tour-content"><p class="review__tour-kicker">Короткое знакомство</p><h2 id="tutorial-heading"></h2><p id="tutorial-text"></p><div class="review__tour-footer"><span class="review__tour-progress" id="tutorial-progress"></span><div class="review__tour-actions"><button id="tutorial-skip" type="button">Пропустить</button><button class="review__button--primary" id="tutorial-next" type="button">Далее</button></div></div></div></dialog>
|
|
|
|
|
<footer class="review__header"><div class="review__header-content">Акылдаш · Frontend v0.1.1 · Backend v0.11.2 · Внутренняя лаборатория релевантности</div></footer>
|
|
|
|
|
<footer class="review__header"><div class="review__header-content">Акылдаш · Frontend v0.1.2 · Backend v0.11.2 · Внутренняя лаборатория релевантности</div></footer>
|
|
|
|
|
<script>
|
|
|
|
|
document.body.classList.add('review__locked');
|
|
|
|
|
const DRAFT_KEY = 'akyldash-search-review-draft';
|
|
|
|
@@ -191,9 +194,27 @@
|
|
|
|
|
const state = { results: [], token: '', selected: null, query: '' };
|
|
|
|
|
const $ = (id) => document.getElementById(id);
|
|
|
|
|
let account = null;
|
|
|
|
|
for (const dialog of [$('login-dialog'), $('password-dialog')]) {
|
|
|
|
|
dialog.addEventListener('cancel', (event) => event.preventDefault());
|
|
|
|
|
}
|
|
|
|
|
document.addEventListener('keydown', (event) => {
|
|
|
|
|
if (event.key !== 'Tab') return;
|
|
|
|
|
const dialog = [$('login-dialog'), $('password-dialog')].find((item) => item.open);
|
|
|
|
|
if (!dialog) return;
|
|
|
|
|
const controls = [...dialog.querySelectorAll('input:not(:disabled), button:not(:disabled)')].filter((item) => item.getClientRects().length);
|
|
|
|
|
const first = controls[0];
|
|
|
|
|
const last = controls[controls.length - 1];
|
|
|
|
|
if (event.shiftKey && (document.activeElement === first || !dialog.contains(document.activeElement))) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
last.focus();
|
|
|
|
|
} else if (!event.shiftKey && (document.activeElement === last || !dialog.contains(document.activeElement))) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
first.focus();
|
|
|
|
|
}
|
|
|
|
|
});
|
|
|
|
|
const reviewRoute = location.pathname === '/review';
|
|
|
|
|
$('review').hidden = !reviewRoute; $('search-page').hidden = reviewRoute; $('review-nav').classList.toggle('is-active', reviewRoute); $('search-nav').classList.toggle('is-active', !reviewRoute);
|
|
|
|
|
if (!reviewRoute) { document.title = 'Поиск по правовой базе · Акылдаш'; document.querySelector('.review__header-content').hidden = true; $('tutorial-open').hidden = true; document.querySelector('.review__skip').href = '#public-results'; document.querySelector('footer .review__header-content').textContent = 'Акылдаш · Frontend v0.1.1 · Backend v0.11.2'; }
|
|
|
|
|
if (!reviewRoute) { document.title = 'Поиск по правовой базе · Акылдаш'; document.querySelector('.review__header-content').hidden = true; $('tutorial-open').hidden = true; document.querySelector('.review__skip').href = '#public-results'; document.querySelector('footer .review__header-content').textContent = 'Акылдаш · Frontend v0.1.2 · Backend v0.11.2'; }
|
|
|
|
|
async function apiFetch(path, options = {}) { const response = await fetch(path, {credentials: 'same-origin', ...options}); if (response.status === 401 && path !== '/auth/login') { document.body.classList.add('review__locked'); if (!$('login-dialog').open) $('login-dialog').showModal(); $('login-username').focus(); } return response; }
|
|
|
|
|
const showLogin = () => { document.body.classList.add('review__locked'); if (!$('login-dialog').open) $('login-dialog').showModal(); $('login-username').focus(); };
|
|
|
|
|
const enterWorkspace = (user) => { account = user; $('signed-in-user').textContent = `${user.username} · ${user.role}`; $('reviewer').value = user.username; $('profile-username').textContent = user.username; $('profile-role').textContent = user.role; $('profile-created').textContent = new Date(user.created_at * 1000).toLocaleDateString('ru-RU'); $('users-open').hidden = user.username.toLowerCase() !== 'admin'; document.body.classList.remove('review__locked'); if (!reviewRoute) initSearchPage(); else if (!hasSeenTour()) setTimeout(openTutorial, 350); };
|
|
|
|
|