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
Откройте меню: нажмите на три полоски слева сверху.
+
-
2
Перейдите в «Команды» и нажмите «Добавить»: задайте название, цвет и пригласите участников по имени или email.
+
-
3
Откройте «Проекты» и создайте первый проект для своей команды.
+
-
4
Внутри проекта добавьте kanban-доску.
+
-
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