From 9234a03f46ccf064c37bfc4186e351b7adc2bf71 Mon Sep 17 00:00:00 2001 From: Lychee <2006tka@gmail.com> Date: Sun, 17 May 2026 20:26:13 +0500 Subject: [PATCH] =?UTF-8?q?=D0=BF=D0=BE=D1=87=D0=B8=D0=BD=D0=B8=D0=BB?= =?UTF-8?q?=D0=B0=20=D1=82=D1=83=D1=82=D0=BE=D1=80=D0=B8=D0=B0=D0=BB=20?= =?UTF-8?q?=D0=B8=20=D0=BB=D0=B8=D0=BD=D0=B8=D1=8E=20=D1=86=D0=B2=D0=B5?= =?UTF-8?q?=D1=82=D0=BD=D1=83=D1=8E=20=D1=83=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=87=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Api/wwwroot/css/kanban.css | 8 ++--- Api/wwwroot/css/styles.css | 9 ++++-- Api/wwwroot/js/auth.js | 11 +++++++ Api/wwwroot/js/script.js | 62 ++++++++++++++++++++++++++++++-------- Api/wwwroot/js/tutorial.js | 34 ++++++++++++++++++--- 5 files changed, 102 insertions(+), 22 deletions(-) diff --git a/Api/wwwroot/css/kanban.css b/Api/wwwroot/css/kanban.css index 2856308..4ad3b87 100644 --- a/Api/wwwroot/css/kanban.css +++ b/Api/wwwroot/css/kanban.css @@ -3596,14 +3596,14 @@ body.sidebar-closed .kanban-head { .task-card::before { content: ""; position: absolute; - left: 1px; - top: 1px; - bottom: 1px; + left: 0; + top: 0; + bottom: 0; width: 6px; background: var(--task-color, var(--orange)); - border-radius: 3px; + border-radius: 12px 0 0 12px; } .task-top { diff --git a/Api/wwwroot/css/styles.css b/Api/wwwroot/css/styles.css index 89243bb..4885afe 100644 --- a/Api/wwwroot/css/styles.css +++ b/Api/wwwroot/css/styles.css @@ -743,15 +743,16 @@ button.section-head:hover { .team-grid { display: grid; - grid-template-columns: 1fr 1fr; + grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 16px 20px; padding-right: calc(var(--sidebar-hover-shift) + 10px); } .team-card { + min-width: 0; min-height: 68px; border-radius: 24px; - padding: 0 26px; + padding: 14px 20px; border: 1px solid #3a3a3a; background: transparent; color: #f4f4f4; @@ -770,8 +771,12 @@ button.section-head:hover { } .team-card span:first-child { + min-width: 0; font-size: 22px; font-weight: 300; + line-height: 1.2; + overflow-wrap: anywhere; + white-space: normal; } .team-card:hover { background: var(--sidebar-hover-bg); diff --git a/Api/wwwroot/js/auth.js b/Api/wwwroot/js/auth.js index cec3dd6..f4e2d3d 100644 --- a/Api/wwwroot/js/auth.js +++ b/Api/wwwroot/js/auth.js @@ -16,6 +16,7 @@ const loginToRegisterBtn = document.getElementById("loginToRegister"); const registerToLoginBtn = document.getElementById("registerToLogin"); const loginForm = document.getElementById("loginForm"); const registerForm = document.getElementById("registerForm"); +const TUTORIAL_PENDING_KEY = "boardify.tutorialPending"; showLoginBtn?.addEventListener("click", () => setMode("login")); showRegisterBtn?.addEventListener("click", () => setMode("register")); @@ -60,6 +61,7 @@ registerForm?.addEventListener("submit", async (event) => { await runAuthAction(registerForm, async () => { await createUser(email, password, name); await login(email, password); + markTutorialPending(); setMessage("Аккаунт создан, открываем Boardify...", "success"); redirectToApp(); }, "Не удалось зарегистрироваться. Возможно, email уже занят."); @@ -134,5 +136,14 @@ function redirectToApp() { }, 250); } +function markTutorialPending() { + try { + localStorage.setItem("boardify.tutorialPending", "true"); + localStorage.removeItem("boardify.tutorialSeen"); + } catch { + // Registration should continue even if localStorage is unavailable. + } +} + const params = new URLSearchParams(window.location.search); setMode(params.get("mode") || "start", { keepMessage: true }); diff --git a/Api/wwwroot/js/script.js b/Api/wwwroot/js/script.js index ba94598..3868192 100644 --- a/Api/wwwroot/js/script.js +++ b/Api/wwwroot/js/script.js @@ -9,6 +9,8 @@ import { selectProjectFromUrl, } from "./boardData.js"; +import { showFirstLoginTutorial } from "./tutorial.js"; + const matrix = document.querySelector("#taskMatrix"); const GROUPS = [ @@ -27,13 +29,25 @@ let state = { function row(task) { const el = document.createElement("article"); el.className = "task-row"; + el.innerHTML = ` -
${escapeHtml(task.title)}
-
-
приоритет${escapeHtml(task.priority)}
-
-
Дедлайн через${escapeHtml(formatTimeLeft(task.deadline))}
- `; +
${escapeHtml(task.title)}
+ +
+ +
+ приоритет + ${escapeHtml(task.priority)} +
+ +
+ +
+ Дедлайн через + ${escapeHtml(formatTimeLeft(task.deadline))} +
+ `; + return el; } @@ -46,13 +60,16 @@ function renderMatrix(tasks) { q.dataset.label = group.label; const groupTasks = tasks.filter((task) => - isImportant(task) === group.important && isUrgent(task) === group.urgent + isImportant(task) === group.important && + isUrgent(task) === group.urgent ); if (groupTasks.length === 0) { q.appendChild(emptyRow("Нет задач")); } else { - groupTasks.slice(0, 4).forEach((task) => q.appendChild(row(task))); + groupTasks.slice(0, 4).forEach((task) => { + q.appendChild(row(task)); + }); } matrix.appendChild(q); @@ -63,6 +80,7 @@ function emptyRow(text) { const el = document.createElement("p"); el.className = "empty-state"; el.textContent = text; + return el; } @@ -78,6 +96,7 @@ function isUrgent(task) { } const diffMs = deadline.getTime() - Date.now(); + return diffMs <= 3 * 24 * 60 * 60 * 1000; } @@ -86,9 +105,18 @@ async function init() { try { const workspace = await loadWorkspace(); + const activeProject = selectProjectFromUrl(workspace.projects); - const boards = await Promise.all(workspace.projects.map((project) => loadBoard(project).catch(() => null))); - const tasks = boards.filter(Boolean).flatMap(getBoardTasks); + + const boards = await Promise.all( + workspace.projects.map((project) => + loadBoard(project).catch(() => null) + ) + ); + + const tasks = boards + .filter(Boolean) + .flatMap(getBoardTasks); state = { workspace, @@ -98,23 +126,33 @@ async function init() { renderSidebar(); renderMatrix(tasks); + + showFirstLoginTutorial(); } catch (error) { console.error(error); + matrix.innerHTML = ""; + GROUPS.forEach((group) => { const q = document.createElement("section"); q.className = "quadrant"; q.dataset.label = group.label; + q.appendChild(emptyRow("Не удалось загрузить API")); + matrix.appendChild(q); }); } } function renderSidebar() { - renderSidebarNavigation(state.workspace, state.activeProjectId, getTaskStats(state.tasks)); + renderSidebarNavigation( + state.workspace, + state.activeProjectId, + getTaskStats(state.tasks) + ); } window.addEventListener("sidebar:loaded", renderSidebar); -init(); +init(); \ No newline at end of file diff --git a/Api/wwwroot/js/tutorial.js b/Api/wwwroot/js/tutorial.js index 3eabd4c..754fb11 100644 --- a/Api/wwwroot/js/tutorial.js +++ b/Api/wwwroot/js/tutorial.js @@ -1,9 +1,10 @@ const TUTORIAL_STORAGE_KEY = "boardify.tutorialSeen"; +const TUTORIAL_PENDING_KEY = "boardify.tutorialPending"; const TUTORIAL_CSS_ID = "boardifyTutorialStyles"; const TUTORIAL_CSS_HREF = "./css/tutorial.css"; export function showFirstLoginTutorial() { - if (wasTutorialSeen() || document.querySelector("[data-tutorial-overlay]")) { + if (!shouldShowTutorial() || document.querySelector("[data-tutorial-overlay]")) { return; } @@ -21,6 +22,7 @@ export function showFirstLoginTutorial() { overlay.querySelector("[data-tutorial-close]")?.addEventListener("click", close); overlay.querySelector("[data-tutorial-done]")?.addEventListener("click", close); + overlay.addEventListener("click", (event) => { if (event.target === overlay) { close(); @@ -34,6 +36,7 @@ export function showFirstLoginTutorial() { }; document.addEventListener("keydown", onKeyDown); + overlay.cleanupTutorial = () => { document.removeEventListener("keydown", onKeyDown); }; @@ -53,6 +56,7 @@ function createTutorialMarkup() {

Быстрый старт

+

С чего начать?

    @@ -60,18 +64,22 @@ function createTutorialMarkup() { 1 Откройте меню: нажмите на три полоски слева сверху. +
  1. 2 Перейдите в «Команды» и нажмите «Добавить»: задайте название, цвет и пригласите участников по имени или email.
  2. +
  3. 3 Откройте «Проекты» и создайте первый проект для своей команды.
  4. +
  5. 4 Внутри проекта добавьте kanban-доску.
  6. +
  7. 5 Готово: как админ команды вы можете создавать колонки, задачи и подзадачи. @@ -87,8 +95,11 @@ function createTutorialMarkup() { function closeTutorial(overlay) { markTutorialSeen(); + clearTutorialPending(); + overlay.classList.remove("is-open"); document.body.classList.remove("tutorial-locked"); + overlay.cleanupTutorial?.(); setTimeout(() => { @@ -105,12 +116,19 @@ function ensureTutorialStyles() { link.id = TUTORIAL_CSS_ID; link.rel = "stylesheet"; link.href = TUTORIAL_CSS_HREF; + document.head.appendChild(link); } -function wasTutorialSeen() { +function shouldShowTutorial() { try { - return localStorage.getItem(TUTORIAL_STORAGE_KEY) === "true"; + const isPending = localStorage.getItem(TUTORIAL_PENDING_KEY) === "true"; + + if (isPending) { + return true; + } + + return false; } catch { return false; } @@ -120,6 +138,14 @@ function markTutorialSeen() { try { localStorage.setItem(TUTORIAL_STORAGE_KEY, "true"); } catch { - // Ignore storage errors: closing the dialog should still work. + // Ignore storage errors. } } + +function clearTutorialPending() { + try { + localStorage.removeItem(TUTORIAL_PENDING_KEY); + } catch { + // Ignore storage errors. + } +} \ No newline at end of file