commited\
This commit is contained in:
+250
@@ -0,0 +1,250 @@
|
||||
let globalBlur = false;
|
||||
let currentDateFilter = "all"; // 'all' | 'today'
|
||||
let currentStatusFilter = "all"; // 'all' | 'pending' | 'completed' | 'delayed' | 'pend_delay'
|
||||
let editingId = null;
|
||||
|
||||
async function fetchTodos() {
|
||||
const res = await fetch("/api/todos");
|
||||
return res.json();
|
||||
}
|
||||
let searchTitle = "";
|
||||
let searchDescription = "";
|
||||
let searchBoth = "";
|
||||
function autoResize(el) {
|
||||
el.style.height = "auto";
|
||||
el.style.height = el.scrollHeight + "px";
|
||||
}
|
||||
|
||||
document.getElementById("todoText").addEventListener("input", (e) => {
|
||||
autoResize(e.target);
|
||||
});
|
||||
document.getElementById("searchTitle").addEventListener("input", (e) => {
|
||||
searchTitle = e.target.value.toLowerCase();
|
||||
renderTodos();
|
||||
});
|
||||
|
||||
document.getElementById("searchDescription").addEventListener("input", (e) => {
|
||||
searchDescription = e.target.value.toLowerCase();
|
||||
renderTodos();
|
||||
});
|
||||
|
||||
document.getElementById("searchBoth").addEventListener("input", (e) => {
|
||||
searchBoth = e.target.value.toLowerCase();
|
||||
renderTodos();
|
||||
});
|
||||
function toggleSidebar() {
|
||||
const sidebar = document.getElementById("sidebar");
|
||||
sidebar.classList.toggle("collapsed");
|
||||
|
||||
const icon = document.getElementById("collapseIcon");
|
||||
icon.textContent = sidebar.classList.contains("collapsed") ? "▶" : "◀";
|
||||
}
|
||||
|
||||
function toggleDarkMode() {
|
||||
document.body.classList.toggle("dark");
|
||||
}
|
||||
|
||||
// Sidebar "Today / All" buttons
|
||||
function filterTodos(type) {
|
||||
currentDateFilter = type;
|
||||
renderTodos();
|
||||
}
|
||||
|
||||
// Status dropdown
|
||||
function filterByStatus(status) {
|
||||
currentStatusFilter = status;
|
||||
renderTodos();
|
||||
}
|
||||
|
||||
async function toggleGlobalBlur() {
|
||||
globalBlur = !globalBlur;
|
||||
const todos = await fetchTodos();
|
||||
// Wait for ALL updates to actually finish before re-rendering
|
||||
await Promise.all(
|
||||
todos.map((todo) =>
|
||||
updateTodo(todo.id, { private: globalBlur }, { render: false }),
|
||||
),
|
||||
);
|
||||
renderTodos();
|
||||
}
|
||||
|
||||
async function deleteTodo(id) {
|
||||
await fetch(`/api/todos/${id}`, { method: "DELETE" });
|
||||
renderTodos();
|
||||
}
|
||||
|
||||
async function updateTodo(id, data, { render = true } = {}) {
|
||||
await fetch(`/api/todos/${id}`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(data),
|
||||
});
|
||||
if (render) await renderTodos();
|
||||
}
|
||||
|
||||
document.getElementById("todoForm").addEventListener("submit", async (e) => {
|
||||
e.preventDefault();
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
const newTodo = {
|
||||
title: document.getElementById("todoTitle").value || "",
|
||||
text: document.getElementById("todoText").value,
|
||||
date: today,
|
||||
status: "pending",
|
||||
private: globalBlur,
|
||||
};
|
||||
await fetch("/api/todos", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(newTodo),
|
||||
});
|
||||
e.target.reset();
|
||||
document.getElementById("todoText").style.height = "auto"; // 👈 add this
|
||||
renderTodos();
|
||||
document.getElementById("todoText").focus();
|
||||
});
|
||||
function startEdit(id) {
|
||||
editingId = id;
|
||||
renderTodos();
|
||||
}
|
||||
|
||||
function cancelEdit() {
|
||||
editingId = null;
|
||||
renderTodos();
|
||||
}
|
||||
|
||||
async function saveEdit(id) {
|
||||
const title = document.getElementById(`editTitle-${id}`).value;
|
||||
const text = document.getElementById(`editText-${id}`).value;
|
||||
editingId = null;
|
||||
await updateTodo(id, { title, text });
|
||||
}
|
||||
|
||||
// Prevent broken HTML if the todo text/title contains quotes, <, etc.
|
||||
function escapeHtml(str) {
|
||||
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
||||
}
|
||||
function escapeAttr(str) {
|
||||
return escapeHtml(str).replace(/"/g, """);
|
||||
}
|
||||
|
||||
function renderTodoCard(todo) {
|
||||
if (editingId === todo.id) {
|
||||
return `
|
||||
<div class="todo ${todo.status} editing">
|
||||
<div class="todo-content">
|
||||
<input type="text" id="editTitle-${todo.id}" value="${escapeAttr(todo.title || "")}" placeholder="Title">
|
||||
<textarea id="editText-${todo.id}">${escapeHtml(todo.text || "")}</textarea>
|
||||
</div>
|
||||
<div class="todo-actions">
|
||||
<button onclick="saveEdit(${todo.id})">Save</button>
|
||||
<button onclick="cancelEdit()">Cancel</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
return `
|
||||
<div class="todo ${todo.status}">
|
||||
<div class="todo-content ${todo.private ? "blur" : ""}">
|
||||
${todo.title ? `<h5>${todo.title}</h5>` : ""}
|
||||
<p class="todo-description">${escapeHtml(todo.text || "")}</p>
|
||||
<small>${todo.date} - ${todo.status}</small> </div>
|
||||
<div class="todo-actions">
|
||||
<button onclick="updateTodo(${todo.id}, {status:'pending'})">Pending</button>
|
||||
<button onclick="updateTodo(${todo.id}, {status:'completed'})">Done</button>
|
||||
<button onclick="updateTodo(${todo.id}, {status:'delayed'})">Urgent</button>
|
||||
<button onclick="startEdit(${todo.id})">Edit</button>
|
||||
<button onclick="deleteTodo(${todo.id})">Delete</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}
|
||||
document.getElementById("todoText").addEventListener("keydown", (e) => {
|
||||
if (e.key === "Enter" && (e.ctrlKey || e.metaKey)) {
|
||||
e.preventDefault();
|
||||
document.getElementById("todoForm").requestSubmit();
|
||||
}
|
||||
});
|
||||
async function renderTodos() {
|
||||
const todos = await fetchTodos();
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
|
||||
let filtered = todos;
|
||||
if (currentDateFilter === "today") {
|
||||
filtered = filtered.filter((t) => t.date === today);
|
||||
}
|
||||
if (currentStatusFilter !== "all") {
|
||||
if (currentStatusFilter === "pend_delay") {
|
||||
filtered = filtered.filter((t) => t.status != "completed");
|
||||
} else {
|
||||
filtered = filtered.filter((t) => t.status === currentStatusFilter);
|
||||
}
|
||||
}
|
||||
if (searchTitle) {
|
||||
filtered = filtered.filter((t) =>
|
||||
(t.title || "").toLowerCase().includes(searchTitle),
|
||||
);
|
||||
}
|
||||
if (searchDescription) {
|
||||
filtered = filtered.filter((t) =>
|
||||
(t.text || "").toLowerCase().includes(searchDescription),
|
||||
);
|
||||
}
|
||||
if (searchBoth) {
|
||||
filtered = filtered.filter(
|
||||
(t) =>
|
||||
(t.title || "").toLowerCase().includes(searchBoth) ||
|
||||
(t.text || "").toLowerCase().includes(searchBoth),
|
||||
);
|
||||
}
|
||||
|
||||
filtered = filtered.slice().reverse(); // newest first
|
||||
|
||||
document.getElementById("todoList").innerHTML = filtered
|
||||
.map(renderTodoCard)
|
||||
.join("");
|
||||
updateAnalytics(todos);
|
||||
}
|
||||
|
||||
function updateAnalytics(todos) {
|
||||
const today = new Date().toISOString().split("T")[0];
|
||||
const todayTodos = todos.filter((t) => t.date === today);
|
||||
|
||||
const completedToday = todayTodos.filter(
|
||||
(t) => t.status === "completed",
|
||||
).length;
|
||||
const pendingToday = todayTodos.filter((t) => t.status === "pending").length;
|
||||
const delayedToday = todayTodos.filter((t) => t.status === "delayed").length;
|
||||
const pend_delay = todayTodos.filter((t) => t.status != "completed").length;
|
||||
|
||||
const allCompleted = todos.filter((t) => t.status === "completed").length;
|
||||
const allPending = todos.filter((t) => t.status === "pending").length;
|
||||
const allDelayed = todos.filter((t) => t.status === "delayed").length;
|
||||
const all_pend_delay = todos.filter((t) => t.status != "completed").length;
|
||||
|
||||
document.getElementById("analytics").innerHTML = `
|
||||
<p><strong>Today</strong></p>
|
||||
<p>Total Today: ${todayTodos.length}</p>
|
||||
<p>Completed Today: ${completedToday}</p>
|
||||
<p>pending+delayed Today: ${pend_delay}</p>
|
||||
<p>Pending Today: ${pendingToday}</p>
|
||||
<p>Delayed Today: ${delayedToday}</p>
|
||||
<hr>
|
||||
<p><strong>Overall</strong></p>
|
||||
<p>All Total: ${todos.length}</p>
|
||||
<p>All Completed: ${allCompleted}</p>
|
||||
<p>All pending+delayed: ${all_pend_delay}</p>
|
||||
<p>All Pending: ${allPending}</p>
|
||||
<p>All Delayed: ${allDelayed}</p>
|
||||
`;
|
||||
}
|
||||
renderTodos();
|
||||
|
||||
window.addEventListener("DOMContentLoaded", async () => {
|
||||
// ✅ add async
|
||||
document.getElementById("sidebar").classList.add("collapsed");
|
||||
document.body.classList.add("dark");
|
||||
document.getElementById("todoText").focus();
|
||||
|
||||
const todos = await fetchTodos();
|
||||
globalBlur = todos.length > 0 && todos.every((t) => t.private);
|
||||
renderTodos(); // also make sure the UI actually reflects restored state
|
||||
});
|
||||
@@ -0,0 +1,65 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Professional TODO App</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="sidebar" id="sidebar">
|
||||
<button onclick="filterTodos('today')"><span class="icon">📅</span><span class="label">Today's
|
||||
Todos</span></button>
|
||||
<button onclick="filterTodos('all')"><span class="icon">📋</span><span class="label">All Todos</span></button>
|
||||
<button onclick="toggleDarkMode()"><span class="icon">🌙</span><span class="label">Dark Mode</span></button>
|
||||
<button onclick="toggleGlobalBlur()"><span class="icon">👁</span><span class="label">Blur/Unblur
|
||||
All</span></button>
|
||||
<button id="collapseBtn" onclick="toggleSidebar()">
|
||||
<span class="icon" id="collapseIcon">▶</span>
|
||||
<span class="label">Collapse</span>
|
||||
</button>
|
||||
|
||||
<div class="status-filters">
|
||||
<button onclick="filterByStatus('all')">
|
||||
<span class="icon">📑</span>
|
||||
</button>
|
||||
<button onclick="filterByStatus('pend_delay')">
|
||||
<span class="icon">❕✔</span>
|
||||
</button>
|
||||
<button onclick="filterByStatus('pending')">
|
||||
<span class="icon">⏳</span>
|
||||
</button>
|
||||
<button onclick="filterByStatus('completed')">
|
||||
<span class="icon">✔</span>
|
||||
</button>
|
||||
|
||||
<button onclick="filterByStatus('delayed')">
|
||||
<span class="icon">⚠</span>
|
||||
</button>
|
||||
|
||||
</div>
|
||||
|
||||
<div id="analytics"></div>
|
||||
</div>
|
||||
|
||||
<div class="main">
|
||||
<div class="search-row">
|
||||
<input type="text" id="searchTitle" placeholder="Search by title" autocomplete="off">
|
||||
<input type="text" id="searchDescription" placeholder="Search by description" autocomplete="off">
|
||||
<input type="text" id="searchBoth" placeholder="Search by both" autocomplete="off">
|
||||
</div>
|
||||
<form id="todoForm">
|
||||
<div class="todoForm-row">
|
||||
<input type="text" id="todoTitle" placeholder="Title" autocomplete="off">
|
||||
<button type="submit" style="background-color: #3a3a3a;">Add to list</button>
|
||||
</div>
|
||||
<textarea id="todoText" placeholder="Description" required autocomplete="off"></textarea>
|
||||
</form>
|
||||
<div id="todoList"></div>
|
||||
</div>
|
||||
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,604 @@
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: "Segoe UI", Arial, sans-serif;
|
||||
background: #f9f9f9;
|
||||
color: #333;
|
||||
display: flex;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.headbar {
|
||||
width: 100%;
|
||||
background: #0078d7;
|
||||
color: #fff;
|
||||
padding: 15px 25px;
|
||||
border-bottom: 2px solid #005a9e;
|
||||
}
|
||||
|
||||
.headbar h1 {
|
||||
margin: 0;
|
||||
font-size: 22px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.headbar p {
|
||||
margin: 4px 0 0;
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
width: 220px;
|
||||
background: #f0f0f0;
|
||||
padding: 20px;
|
||||
border-right: 1px solid #ddd;
|
||||
transition: width 0.3s ease, padding 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px;
|
||||
border: none;
|
||||
background: #0078d7;
|
||||
color: #fff;
|
||||
font-weight: 500;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
.sidebar button .icon {
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
.sidebar button:hover {
|
||||
background: #005a9e;
|
||||
}
|
||||
|
||||
#analytics {
|
||||
margin-top: 20px;
|
||||
font-size: 14px;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .label,
|
||||
.sidebar.collapsed #analytics {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar.collapsed button {
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.main {
|
||||
flex: 1;
|
||||
padding: 30px;
|
||||
padding-top: 15px;
|
||||
}
|
||||
|
||||
/* form#todoForm {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 10px;
|
||||
margin-bottom: 20px;
|
||||
} */
|
||||
|
||||
form#todoForm input,
|
||||
form#todoForm textarea,
|
||||
form#todoForm button,
|
||||
form#todoForm label {
|
||||
/* padding: 8px; */
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
form#todoForm input[type="text"],
|
||||
form#todoForm input[type="date"],
|
||||
form#todoForm textarea {
|
||||
flex: 1;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
|
||||
form#todoForm button {
|
||||
background: #28a745;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
|
||||
form#todoForm button:hover {
|
||||
background: #1e7e34;
|
||||
}
|
||||
|
||||
.todo {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 6px;
|
||||
margin: 6px 0;
|
||||
padding: 3px 8px;
|
||||
background: #fff;
|
||||
transition: transform 0.2s ease;
|
||||
max-width: 100%;
|
||||
overflow-x: hidden;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.todo:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.todo-content {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.todo-content p {
|
||||
margin: 4px 0 2px;
|
||||
font-size: 13px;
|
||||
color: #333;
|
||||
white-space: normal;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.todo-content small {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.todo.pending {
|
||||
border-left: 4px solid #ffc107;
|
||||
}
|
||||
|
||||
.todo.completed {
|
||||
border-left: 4px solid #28a745;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.todo.delayed {
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
|
||||
.todo.blur {
|
||||
filter: blur(4px);
|
||||
}
|
||||
|
||||
|
||||
.todo-actions {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.todo-actions button {
|
||||
padding: 3px 6px;
|
||||
font-size: 0.75rem;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
background: #2a2a2a;
|
||||
color: #eee;
|
||||
transition: background 0.15s ease;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.todo-actions button:nth-child(1) {
|
||||
background: #ffc107;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
.todo-actions button:nth-child(2) {
|
||||
background: #28a745;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.todo-actions button:nth-child(3) {
|
||||
background: #dc3545;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.todo-actions button:nth-child(4) {
|
||||
background: #6c757d;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.todo-actions button:nth-child(5) {
|
||||
background: #0078d7;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark {
|
||||
background: #1e1e1e;
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.dark .sidebar {
|
||||
background: #2c2c2c;
|
||||
border-right: 1px solid #444;
|
||||
}
|
||||
|
||||
.dark .sidebar button {
|
||||
background: #444;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.dark .sidebar button:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.dark .main {
|
||||
background: #1e1e1e;
|
||||
}
|
||||
|
||||
.dark .todo {
|
||||
background: #2c2c2c;
|
||||
border: 1px solid #444;
|
||||
}
|
||||
|
||||
.dark .todo h5 {
|
||||
color: #119272;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .label {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar.collapsed #analytics {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.todo-content h3 {
|
||||
margin: 0 0 4px;
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: #0078d7;
|
||||
}
|
||||
|
||||
.dark .todo-content h5 {
|
||||
color: #349ca3;
|
||||
}
|
||||
|
||||
.dark .todo-content p {
|
||||
color: #e0e0e0;
|
||||
}
|
||||
|
||||
.dark .todo-content small {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
.dark .todo.pending {
|
||||
border-left: 4px solid #ffc107;
|
||||
}
|
||||
|
||||
.dark .todo.completed {
|
||||
border-left: 4px solid #28a745;
|
||||
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.dark .todo.delayed {
|
||||
border-left: 4px solid #dc3545;
|
||||
}
|
||||
|
||||
.todo-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.todo-header h3 {
|
||||
margin: 0;
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: #0078d7;
|
||||
}
|
||||
|
||||
.todo-header small {
|
||||
font-size: 12px;
|
||||
color: #666;
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
.dark .todo-header h3 {
|
||||
color: #4da3ff;
|
||||
}
|
||||
|
||||
.dark .todo-header small {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
#statusFilter {
|
||||
/* background: #0078d7; */
|
||||
margin-top: 10px;
|
||||
width: 100%;
|
||||
padding: 6px;
|
||||
border-radius: 4px;
|
||||
border: 1px solid #ccc;
|
||||
background: #444;
|
||||
color: #fff;
|
||||
|
||||
}
|
||||
|
||||
|
||||
#todoText {
|
||||
flex: 1;
|
||||
min-height: 80px;
|
||||
border: 1px solid #ccc;
|
||||
border-radius: 4px;
|
||||
padding: 8px;
|
||||
font-size: 14px;
|
||||
resize: vertical;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.dark input[type="text"],
|
||||
.dark textarea {
|
||||
background-color: #222;
|
||||
color: #e0e0e0;
|
||||
border: 1px solid #555;
|
||||
}
|
||||
|
||||
.dark input[type="text"]::placeholder,
|
||||
.dark textarea::placeholder {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
#analytics hr {
|
||||
margin: 10px 0;
|
||||
border: none;
|
||||
border-top: 1px solid #ccc;
|
||||
}
|
||||
|
||||
.dark #analytics hr {
|
||||
border-top: 1px solid #444;
|
||||
}
|
||||
|
||||
#analytics p {
|
||||
margin: 2px 0;
|
||||
}
|
||||
|
||||
.todo-actions button:hover {
|
||||
background: #3a3a3a;
|
||||
}
|
||||
|
||||
.todo.editing {
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #444;
|
||||
border-radius: 10px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.todo.editing .todo-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.todo.editing input,
|
||||
.todo.editing textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #2a2a2a;
|
||||
color: #eee;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 8px 10px;
|
||||
font-family: inherit;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.todo.editing input:focus,
|
||||
.todo.editing textarea:focus {
|
||||
outline: none;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
.todo.editing textarea {
|
||||
min-height: 70px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.todo.editing .todo-actions {
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
}
|
||||
|
||||
.todo.editing .todo-actions button:first-child {
|
||||
background: #2d6a4f;
|
||||
}
|
||||
|
||||
.todo.editing .todo-actions button:first-child:hover {
|
||||
background: #37845f;
|
||||
}
|
||||
|
||||
.todo.editing .todo-actions button:last-child {
|
||||
background: #6a2d2d;
|
||||
}
|
||||
|
||||
.todo.editing .todo-actions button:last-child:hover {
|
||||
background: #843737;
|
||||
}
|
||||
|
||||
#todoForm {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
background: #1e1e1e;
|
||||
border: 1px solid #444;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
#todoForm input,
|
||||
#todoForm textarea {
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
background: #2a2a2a;
|
||||
color: #eee;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 3px 6px;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
display: block;
|
||||
align-self: stretch;
|
||||
}
|
||||
|
||||
#todoForm input:focus,
|
||||
#todoForm textarea:focus {
|
||||
outline: none;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
#todoForm textarea {
|
||||
min-height: 26px;
|
||||
resize: vertical;
|
||||
max-height: 50px;
|
||||
line-height: 1.2;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
display: block;
|
||||
align-self: flex-start;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#todoForm button {
|
||||
align-self: flex-start;
|
||||
padding: 3px 10px;
|
||||
font-size: 0.7rem;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
background: #2d6a4f;
|
||||
color: #eee;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
#todoForm button:hover {
|
||||
background: #37845f;
|
||||
}
|
||||
|
||||
.todoForm-row {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
#todoTitle {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.todo.blur .todo-content,
|
||||
.todo.blur .todo-content p:hover {
|
||||
filter: none;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.search-row input {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
box-sizing: border-box;
|
||||
background: #2a2a2a;
|
||||
color: #eee;
|
||||
border: 1px solid #444;
|
||||
border-radius: 4px;
|
||||
padding: 3px 6px;
|
||||
font-family: inherit;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.search-row input:focus {
|
||||
outline: none;
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
.search-row input::placeholder {
|
||||
color: #888;
|
||||
}
|
||||
|
||||
.status-filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* stack vertically in sidebar */
|
||||
gap: 8px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.status-filters button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 6px 10px;
|
||||
font-size: 14px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
background: #444;
|
||||
color: #fff;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.status-filters button:hover {
|
||||
background: #666;
|
||||
}
|
||||
|
||||
.status-filters .icon {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.todo.blur:hover {
|
||||
filter: none
|
||||
}
|
||||
|
||||
form#todoForm textarea {
|
||||
min-height: 50px;
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
.todo-content p {
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
.todo-content.blur {
|
||||
filter: blur(5px);
|
||||
user-select: auto
|
||||
/* transition: filter 0.1s ease; */
|
||||
}
|
||||
|
||||
.todo-content.blur:hover {
|
||||
filter: blur(0);
|
||||
}
|
||||
.blur {
|
||||
filter: blur(4px);
|
||||
}
|
||||
Reference in New Issue
Block a user