mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
feat(fluent): add project creation UI with sorting capabilities
- Add CustomProject interface for project persistence - Implement ProjectPopover (desktop) and ProjectModal (mobile) for project creation - Add color picker with 15 predefined colors for visual organization - Replace add button with sort menu in sidebar - Implement 6 sort options (name, tasks, created date) with localStorage persistence - Use Intl.Collator for locale-sensitive string comparison - Extend Obsidian Component class for proper lifecycle management - Add comprehensive CSS styling for popover and modal interfaces
This commit is contained in:
parent
c747e2bb14
commit
9f008db1a0
11 changed files with 1456 additions and 29 deletions
|
|
@ -509,6 +509,15 @@ export interface ProjectNamingStrategy {
|
|||
enabled: boolean;
|
||||
}
|
||||
|
||||
/** Custom project definition for V2 */
|
||||
export interface CustomProject {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
createdAt: number;
|
||||
updatedAt: number;
|
||||
}
|
||||
|
||||
/** Enhanced project configuration */
|
||||
export interface ProjectConfiguration {
|
||||
/** Path-based project mappings */
|
||||
|
|
@ -523,6 +532,8 @@ export interface ProjectConfiguration {
|
|||
metadataMappings: MetadataMapping[];
|
||||
/** Default project naming strategy */
|
||||
defaultProjectNaming: ProjectNamingStrategy;
|
||||
/** Custom projects for V2 */
|
||||
customProjects?: CustomProject[];
|
||||
}
|
||||
|
||||
/** File parsing configuration for extracting tasks from file metadata and tags */
|
||||
|
|
|
|||
|
|
@ -365,6 +365,40 @@ export class KanbanComponent extends Component {
|
|||
});
|
||||
}
|
||||
|
||||
// Allow multiple symbols to represent the same logical status (e.g., In Progress: "/" and ">")
|
||||
private getAllowedMarksForStatusName(
|
||||
statusName: string
|
||||
): Set<string> | null {
|
||||
if (!statusName) return null;
|
||||
const s = statusName.trim().toLowerCase();
|
||||
const ts = this.plugin.settings.taskStatuses as any;
|
||||
if (!ts) return null;
|
||||
// Map common status names to configured categories
|
||||
const keyMap: Record<string, string> = {
|
||||
"in progress": "inProgress",
|
||||
completed: "completed",
|
||||
abandoned: "abandoned",
|
||||
planned: "planned",
|
||||
"not started": "notStarted",
|
||||
// synonyms commonly seen
|
||||
cancelled: "abandoned",
|
||||
canceled: "abandoned",
|
||||
unchecked: "notStarted",
|
||||
checked: "completed",
|
||||
};
|
||||
const key = keyMap[s];
|
||||
if (!key) return null;
|
||||
const raw: string | undefined = ts[key];
|
||||
if (!raw || typeof raw !== "string") return null;
|
||||
const set = new Set(
|
||||
raw
|
||||
.split("|")
|
||||
.map((ch: string) => ch.trim())
|
||||
.filter((ch: string) => ch.length === 1)
|
||||
);
|
||||
return set.size > 0 ? set : null;
|
||||
}
|
||||
|
||||
// Handle filter application from clickable metadata
|
||||
private handleFilterApply = (
|
||||
filterType: string,
|
||||
|
|
@ -722,19 +756,18 @@ export class KanbanComponent extends Component {
|
|||
}
|
||||
|
||||
private getTasksForStatus(statusName: string): Task[] {
|
||||
// Prefer multi-mark mapping from settings.taskStatuses when available
|
||||
const allowed = this.getAllowedMarksForStatusName(statusName);
|
||||
const statusMark = this.resolveStatusMark(statusName) ?? " ";
|
||||
|
||||
// Filter from the already filtered list
|
||||
const tasksForStatus = this.tasks.filter((task) => {
|
||||
const taskStatusMark = task.status || " ";
|
||||
return taskStatusMark === statusMark;
|
||||
const mark = task.status || " ";
|
||||
return allowed ? allowed.has(mark) : mark === statusMark;
|
||||
});
|
||||
|
||||
// Sort tasks within the status column based on selected sort option
|
||||
tasksForStatus.sort((a, b) => {
|
||||
return this.compareTasks(a, b, this.sortOption);
|
||||
});
|
||||
|
||||
tasksForStatus.sort((a, b) => this.compareTasks(a, b, this.sortOption));
|
||||
return tasksForStatus;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import { V2Sidebar } from "./components/V2Sidebar";
|
|||
import "./styles/v2.css";
|
||||
import "./styles/v2-enhanced.css";
|
||||
import "./styles/v2-content-header.css";
|
||||
import "@/styles/v2-project-popover.css";
|
||||
import { TopNavigation, ViewMode } from "./components/V2TopNavigation";
|
||||
import { Workspace, V2ViewState } from "./types";
|
||||
import { TaskListItemComponent } from "../../components/features/task/view/listItem";
|
||||
|
|
@ -319,6 +320,8 @@ export class TaskViewV2 extends ItemView {
|
|||
(workspace) => this.handleWorkspaceChange(workspace),
|
||||
(projectId) => this.handleProjectSelect(projectId)
|
||||
);
|
||||
// Add sidebar as a child component for proper lifecycle management
|
||||
this.addChild(this.sidebar);
|
||||
}
|
||||
|
||||
private initializeTopNavigation(containerEl: HTMLElement) {
|
||||
|
|
@ -973,6 +976,11 @@ export class TaskViewV2 extends ItemView {
|
|||
"[TG-V2] Using renderContentWithViewMode for non-list/tree mode:",
|
||||
this.viewState.viewMode
|
||||
);
|
||||
// BUGFIX: ensure filters are applied for the newly selected view (e.g., Today/Upcoming)
|
||||
console.log(
|
||||
"[TG-V2] Applying filters before renderContentWithViewMode"
|
||||
);
|
||||
this.applyFilters();
|
||||
this.renderContentWithViewMode();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,32 +1,72 @@
|
|||
import { setIcon } from "obsidian";
|
||||
import { Component, Platform, setIcon, Menu } from "obsidian";
|
||||
import TaskProgressBarPlugin from "../../../index";
|
||||
import { Task } from "../../../types/task";
|
||||
import { ProjectPopover, ProjectModal } from "./ProjectPopover";
|
||||
import type { CustomProject } from "../../../common/setting-definition";
|
||||
|
||||
interface Project {
|
||||
id: string;
|
||||
name: string;
|
||||
color: string;
|
||||
taskCount: number;
|
||||
createdAt?: number;
|
||||
updatedAt?: number;
|
||||
}
|
||||
|
||||
export class ProjectList {
|
||||
type SortOption =
|
||||
| "name-asc"
|
||||
| "name-desc"
|
||||
| "tasks-asc"
|
||||
| "tasks-desc"
|
||||
| "created-asc"
|
||||
| "created-desc";
|
||||
|
||||
export class ProjectList extends Component {
|
||||
private containerEl: HTMLElement;
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private projects: Project[] = [];
|
||||
private activeProjectId: string | null = null;
|
||||
private onProjectSelect: (projectId: string) => void;
|
||||
private currentPopover: ProjectPopover | null = null;
|
||||
private currentSort: SortOption = "name-asc";
|
||||
private readonly STORAGE_KEY = "task-genius-project-sort";
|
||||
private collator: Intl.Collator;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
private onProjectSelect: (projectId: string) => void
|
||||
onProjectSelect: (projectId: string) => void
|
||||
) {
|
||||
super();
|
||||
this.containerEl = containerEl;
|
||||
this.plugin = plugin;
|
||||
this.onProjectSelect = onProjectSelect;
|
||||
|
||||
this.loadProjects();
|
||||
// Initialize collator with locale-sensitive sorting
|
||||
// Use numeric option to handle numbers naturally (e.g., "Project 2" < "Project 10")
|
||||
this.collator = new Intl.Collator(undefined, {
|
||||
numeric: true,
|
||||
sensitivity: "base", // Case-insensitive comparison
|
||||
});
|
||||
}
|
||||
|
||||
async onload() {
|
||||
await this.loadSortPreference();
|
||||
await this.loadProjects();
|
||||
this.render();
|
||||
}
|
||||
|
||||
onunload() {
|
||||
// Clean up any open popover
|
||||
if (this.currentPopover) {
|
||||
this.removeChild(this.currentPopover);
|
||||
this.currentPopover = null;
|
||||
}
|
||||
|
||||
// Clear container
|
||||
this.containerEl.empty();
|
||||
}
|
||||
|
||||
private async loadProjects() {
|
||||
let tasks: Task[] = [];
|
||||
if (this.plugin.dataflowOrchestrator) {
|
||||
|
|
@ -56,9 +96,62 @@ export class ProjectList {
|
|||
});
|
||||
|
||||
this.projects = Array.from(projectMap.values());
|
||||
|
||||
// Load custom projects
|
||||
this.loadCustomProjects();
|
||||
|
||||
// Apply sorting
|
||||
this.sortProjects();
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
private async loadSortPreference() {
|
||||
const saved = await this.plugin.app.loadLocalStorage(this.STORAGE_KEY);
|
||||
if (saved && this.isValidSortOption(saved)) {
|
||||
this.currentSort = saved as SortOption;
|
||||
}
|
||||
}
|
||||
|
||||
private async saveSortPreference() {
|
||||
await this.plugin.app.saveLocalStorage(
|
||||
this.STORAGE_KEY,
|
||||
this.currentSort
|
||||
);
|
||||
}
|
||||
|
||||
private isValidSortOption(value: string): boolean {
|
||||
return [
|
||||
"name-asc",
|
||||
"name-desc",
|
||||
"tasks-asc",
|
||||
"tasks-desc",
|
||||
"created-asc",
|
||||
"created-desc",
|
||||
].includes(value);
|
||||
}
|
||||
|
||||
private sortProjects() {
|
||||
this.projects.sort((a, b) => {
|
||||
switch (this.currentSort) {
|
||||
case "name-asc":
|
||||
return this.collator.compare(a.name, b.name);
|
||||
case "name-desc":
|
||||
return this.collator.compare(b.name, a.name);
|
||||
case "tasks-asc":
|
||||
return a.taskCount - b.taskCount;
|
||||
case "tasks-desc":
|
||||
return b.taskCount - a.taskCount;
|
||||
case "created-asc":
|
||||
return (a.createdAt || 0) - (b.createdAt || 0);
|
||||
case "created-desc":
|
||||
return (b.createdAt || 0) - (a.createdAt || 0);
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private generateColorForProject(projectName: string): string {
|
||||
const colors = [
|
||||
"#e74c3c",
|
||||
|
|
@ -112,7 +205,7 @@ export class ProjectList {
|
|||
text: String(project.taskCount),
|
||||
});
|
||||
|
||||
projectItem.addEventListener("click", () => {
|
||||
this.registerDomEvent(projectItem, "click", () => {
|
||||
this.setActiveProject(project.id);
|
||||
this.onProjectSelect(project.id);
|
||||
});
|
||||
|
|
@ -131,8 +224,8 @@ export class ProjectList {
|
|||
text: "Add Project",
|
||||
});
|
||||
|
||||
addProjectBtn.addEventListener("click", () => {
|
||||
console.log("Add new project");
|
||||
this.registerDomEvent(addProjectBtn, "click", () => {
|
||||
this.handleAddProject(addProjectBtn);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -160,4 +253,175 @@ export class ProjectList {
|
|||
public refresh() {
|
||||
this.loadProjects();
|
||||
}
|
||||
|
||||
private handleAddProject(buttonEl: HTMLElement) {
|
||||
// Clean up any existing popover
|
||||
if (this.currentPopover) {
|
||||
this.removeChild(this.currentPopover);
|
||||
this.currentPopover = null;
|
||||
}
|
||||
|
||||
if (Platform.isMobile) {
|
||||
// Mobile: Use Obsidian Modal
|
||||
const modal = new ProjectModal(
|
||||
this.plugin.app,
|
||||
this.plugin,
|
||||
async (project) => {
|
||||
await this.saveProject(project);
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
} else {
|
||||
// Desktop: Use popover
|
||||
this.currentPopover = new ProjectPopover(
|
||||
this.plugin,
|
||||
buttonEl,
|
||||
async (project) => {
|
||||
await this.saveProject(project);
|
||||
if (this.currentPopover) {
|
||||
this.removeChild(this.currentPopover);
|
||||
this.currentPopover = null;
|
||||
}
|
||||
},
|
||||
() => {
|
||||
if (this.currentPopover) {
|
||||
this.removeChild(this.currentPopover);
|
||||
this.currentPopover = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
this.addChild(this.currentPopover);
|
||||
}
|
||||
}
|
||||
|
||||
private async saveProject(project: CustomProject) {
|
||||
// Initialize customProjects if it doesn't exist
|
||||
if (!this.plugin.settings.projectConfig) {
|
||||
this.plugin.settings.projectConfig = {
|
||||
enableEnhancedProject: false,
|
||||
pathMappings: [],
|
||||
metadataConfig: {
|
||||
metadataKey: "project",
|
||||
enabled: false,
|
||||
},
|
||||
configFile: {
|
||||
fileName: "project.md",
|
||||
searchRecursively: true,
|
||||
enabled: false,
|
||||
},
|
||||
metadataMappings: [],
|
||||
defaultProjectNaming: {
|
||||
strategy: "filename",
|
||||
stripExtension: true,
|
||||
enabled: false,
|
||||
},
|
||||
customProjects: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (!this.plugin.settings.projectConfig.customProjects) {
|
||||
this.plugin.settings.projectConfig.customProjects = [];
|
||||
}
|
||||
|
||||
// Add the new project
|
||||
this.plugin.settings.projectConfig.customProjects.push(project);
|
||||
|
||||
// Save settings
|
||||
await this.plugin.saveSettings();
|
||||
|
||||
// Refresh the project list
|
||||
this.loadProjects();
|
||||
}
|
||||
|
||||
private loadCustomProjects() {
|
||||
const customProjects =
|
||||
this.plugin.settings.projectConfig?.customProjects || [];
|
||||
|
||||
// Merge custom projects into the projects array
|
||||
customProjects.forEach((customProject) => {
|
||||
// Check if project already exists by name
|
||||
const existingIndex = this.projects.findIndex(
|
||||
(p) => p.name === customProject.name
|
||||
);
|
||||
|
||||
if (existingIndex === -1) {
|
||||
// Add new custom project
|
||||
this.projects.push({
|
||||
id: customProject.id,
|
||||
name: customProject.name,
|
||||
color: customProject.color,
|
||||
taskCount: 0, // Will be updated by task counting
|
||||
createdAt: customProject.createdAt,
|
||||
updatedAt: customProject.updatedAt,
|
||||
});
|
||||
} else {
|
||||
// Update existing project with custom color
|
||||
this.projects[existingIndex].id = customProject.id;
|
||||
this.projects[existingIndex].color = customProject.color;
|
||||
this.projects[existingIndex].createdAt =
|
||||
customProject.createdAt;
|
||||
this.projects[existingIndex].updatedAt =
|
||||
customProject.updatedAt;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private showSortMenu(buttonEl: HTMLElement) {
|
||||
const menu = new Menu();
|
||||
|
||||
const sortOptions: {
|
||||
label: string;
|
||||
value: SortOption;
|
||||
icon: string;
|
||||
}[] = [
|
||||
{ label: "Name (A-Z)", value: "name-asc", icon: "arrow-up-a-z" },
|
||||
{ label: "Name (Z-A)", value: "name-desc", icon: "arrow-down-a-z" },
|
||||
{
|
||||
label: "Tasks (Low to High)",
|
||||
value: "tasks-asc",
|
||||
icon: "arrow-up-1-0",
|
||||
},
|
||||
{
|
||||
label: "Tasks (High to Low)",
|
||||
value: "tasks-desc",
|
||||
icon: "arrow-down-1-0",
|
||||
},
|
||||
{
|
||||
label: "Created (Oldest First)",
|
||||
value: "created-asc",
|
||||
icon: "clock",
|
||||
},
|
||||
{
|
||||
label: "Created (Newest First)",
|
||||
value: "created-desc",
|
||||
icon: "history",
|
||||
},
|
||||
];
|
||||
|
||||
sortOptions.forEach((option) => {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(option.label)
|
||||
.setIcon(option.icon)
|
||||
.onClick(async () => {
|
||||
this.currentSort = option.value;
|
||||
await this.saveSortPreference();
|
||||
this.sortProjects();
|
||||
this.render();
|
||||
});
|
||||
if (this.currentSort === option.value) {
|
||||
item.setChecked(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(
|
||||
new MouseEvent("click", {
|
||||
view: window,
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
clientX: buttonEl.getBoundingClientRect().left,
|
||||
clientY: buttonEl.getBoundingClientRect().bottom,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
441
src/experimental/v2/components/ProjectPopover.ts
Normal file
441
src/experimental/v2/components/ProjectPopover.ts
Normal file
|
|
@ -0,0 +1,441 @@
|
|||
import { Component, Platform, Modal, App } from "obsidian";
|
||||
import { createPopper, Instance as PopperInstance } from "@popperjs/core";
|
||||
import TaskProgressBarPlugin from "../../../index";
|
||||
import type { CustomProject } from "../../../common/setting-definition";
|
||||
|
||||
export class ProjectPopover extends Component {
|
||||
private popoverEl: HTMLElement | null = null;
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private onSave: (project: CustomProject) => void;
|
||||
private onClose: () => void;
|
||||
private nameInput: HTMLInputElement | null = null;
|
||||
private selectedColor: string = "#3498db";
|
||||
private popperInstance: PopperInstance | null = null;
|
||||
private referenceEl: HTMLElement;
|
||||
private clickHandler: ((e: MouseEvent) => void) | null = null;
|
||||
private escapeHandler: ((e: KeyboardEvent) => void) | null = null;
|
||||
|
||||
private readonly colors = [
|
||||
"#e74c3c",
|
||||
"#3498db",
|
||||
"#2ecc71",
|
||||
"#f39c12",
|
||||
"#9b59b6",
|
||||
"#1abc9c",
|
||||
"#34495e",
|
||||
"#e67e22",
|
||||
"#16a085",
|
||||
"#27ae60",
|
||||
"#2980b9",
|
||||
"#8e44ad",
|
||||
"#2c3e50",
|
||||
"#f1c40f",
|
||||
"#d35400"
|
||||
];
|
||||
|
||||
constructor(
|
||||
plugin: TaskProgressBarPlugin,
|
||||
referenceEl: HTMLElement,
|
||||
onSave: (project: CustomProject) => void,
|
||||
onClose: () => void
|
||||
) {
|
||||
super();
|
||||
this.plugin = plugin;
|
||||
this.referenceEl = referenceEl;
|
||||
this.onSave = onSave;
|
||||
this.onClose = onClose;
|
||||
}
|
||||
|
||||
onload() {
|
||||
this.createPopover();
|
||||
this.setupPopper();
|
||||
this.setupEventHandlers();
|
||||
}
|
||||
|
||||
onunload() {
|
||||
this.cleanup();
|
||||
}
|
||||
|
||||
private createPopover() {
|
||||
this.popoverEl = document.createElement("div");
|
||||
this.popoverEl.addClass("v2-project-popover-container");
|
||||
document.body.appendChild(this.popoverEl);
|
||||
|
||||
const popover = this.popoverEl.createDiv({ cls: "v2-project-popover" });
|
||||
|
||||
// Arrow
|
||||
const arrow = popover.createDiv({ cls: "v2-popover-arrow" });
|
||||
arrow.setAttribute("data-popper-arrow", "");
|
||||
|
||||
// Content
|
||||
const content = popover.createDiv({ cls: "v2-popover-content" });
|
||||
|
||||
// Title
|
||||
const header = content.createDiv({ cls: "v2-popover-header" });
|
||||
header.createEl("h3", { text: "New Project" });
|
||||
|
||||
// Name input
|
||||
const nameSection = content.createDiv({ cls: "v2-popover-section" });
|
||||
nameSection.createEl("label", { text: "Project Name" });
|
||||
this.nameInput = nameSection.createEl("input", {
|
||||
cls: "v2-popover-input",
|
||||
attr: {
|
||||
type: "text",
|
||||
placeholder: "Enter project name",
|
||||
}
|
||||
});
|
||||
|
||||
// Color picker
|
||||
const colorSection = content.createDiv({ cls: "v2-popover-section" });
|
||||
colorSection.createEl("label", { text: "Choose Color" });
|
||||
|
||||
const colorGrid = colorSection.createDiv({ cls: "v2-color-grid" });
|
||||
this.colors.forEach(color => {
|
||||
const colorButton = colorGrid.createDiv({
|
||||
cls: "v2-color-button",
|
||||
attr: { "data-color": color }
|
||||
});
|
||||
colorButton.style.backgroundColor = color;
|
||||
|
||||
if (color === this.selectedColor) {
|
||||
colorButton.addClass("is-selected");
|
||||
}
|
||||
|
||||
this.registerDomEvent(colorButton, "click", () => {
|
||||
this.selectColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
// Action buttons
|
||||
const actions = content.createDiv({ cls: "v2-popover-actions" });
|
||||
|
||||
const cancelBtn = actions.createEl("button", {
|
||||
cls: "v2-button v2-button-secondary",
|
||||
text: "Cancel"
|
||||
});
|
||||
this.registerDomEvent(cancelBtn, "click", () => this.close());
|
||||
|
||||
const saveBtn = actions.createEl("button", {
|
||||
cls: "v2-button v2-button-primary",
|
||||
text: "Create"
|
||||
});
|
||||
this.registerDomEvent(saveBtn, "click", () => this.save());
|
||||
|
||||
// Handle Enter key on input
|
||||
this.registerDomEvent(this.nameInput, "keydown", (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && this.nameInput?.value.trim()) {
|
||||
this.save();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus input
|
||||
setTimeout(() => this.nameInput?.focus(), 50);
|
||||
}
|
||||
|
||||
private setupPopper() {
|
||||
if (!this.popoverEl) return;
|
||||
|
||||
const popover = this.popoverEl.querySelector(".v2-project-popover") as HTMLElement;
|
||||
if (!popover) return;
|
||||
|
||||
this.popperInstance = createPopper(this.referenceEl, popover, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, 8],
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "arrow",
|
||||
options: {
|
||||
element: ".v2-popover-arrow",
|
||||
padding: 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "preventOverflow",
|
||||
options: {
|
||||
boundary: "viewport",
|
||||
padding: 8,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "flip",
|
||||
options: {
|
||||
fallbackPlacements: ["top-start", "right", "left"],
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
}
|
||||
|
||||
private setupEventHandlers() {
|
||||
// Click outside to close
|
||||
this.clickHandler = (e: MouseEvent) => {
|
||||
if (
|
||||
this.popoverEl &&
|
||||
!this.popoverEl.contains(e.target as Node) &&
|
||||
!this.referenceEl.contains(e.target as Node)
|
||||
) {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
|
||||
// Delay to avoid immediate close
|
||||
setTimeout(() => {
|
||||
if (this.clickHandler) {
|
||||
document.addEventListener("click", this.clickHandler);
|
||||
}
|
||||
}, 0);
|
||||
|
||||
// Escape key to close
|
||||
this.escapeHandler = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
this.close();
|
||||
}
|
||||
};
|
||||
document.addEventListener("keydown", this.escapeHandler);
|
||||
}
|
||||
|
||||
private selectColor(color: string) {
|
||||
if (!this.popoverEl) return;
|
||||
|
||||
this.selectedColor = color;
|
||||
|
||||
// Update selected state
|
||||
this.popoverEl.querySelectorAll(".v2-color-button").forEach(btn => {
|
||||
btn.removeClass("is-selected");
|
||||
});
|
||||
const selectedBtn = this.popoverEl.querySelector(`[data-color="${color}"]`);
|
||||
selectedBtn?.addClass("is-selected");
|
||||
}
|
||||
|
||||
private save() {
|
||||
if (!this.nameInput) return;
|
||||
|
||||
const name = this.nameInput.value.trim();
|
||||
if (!name) {
|
||||
this.nameInput.focus();
|
||||
this.nameInput.addClass("is-error");
|
||||
setTimeout(() => this.nameInput?.removeClass("is-error"), 300);
|
||||
return;
|
||||
}
|
||||
|
||||
const project: CustomProject = {
|
||||
id: `project-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
name,
|
||||
color: this.selectedColor,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
};
|
||||
|
||||
this.onSave(project);
|
||||
this.close();
|
||||
}
|
||||
|
||||
private close() {
|
||||
this.onClose();
|
||||
this.unload();
|
||||
}
|
||||
|
||||
private cleanup() {
|
||||
// Clean up event listeners
|
||||
if (this.clickHandler) {
|
||||
document.removeEventListener("click", this.clickHandler);
|
||||
this.clickHandler = null;
|
||||
}
|
||||
if (this.escapeHandler) {
|
||||
document.removeEventListener("keydown", this.escapeHandler);
|
||||
this.escapeHandler = null;
|
||||
}
|
||||
|
||||
// Clean up Popper instance
|
||||
if (this.popperInstance) {
|
||||
this.popperInstance.destroy();
|
||||
this.popperInstance = null;
|
||||
}
|
||||
|
||||
// Remove DOM elements
|
||||
if (this.popoverEl) {
|
||||
this.popoverEl.remove();
|
||||
this.popoverEl = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class ProjectModal extends Modal {
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private onSave: (project: CustomProject) => void;
|
||||
private nameInput: HTMLInputElement | null = null;
|
||||
private selectedColor: string = "#3498db";
|
||||
private eventListeners: Array<{ element: HTMLElement; event: string; handler: EventListener }> = [];
|
||||
|
||||
private readonly colors = [
|
||||
"#e74c3c",
|
||||
"#3498db",
|
||||
"#2ecc71",
|
||||
"#f39c12",
|
||||
"#9b59b6",
|
||||
"#1abc9c",
|
||||
"#34495e",
|
||||
"#e67e22",
|
||||
"#16a085",
|
||||
"#27ae60",
|
||||
"#2980b9",
|
||||
"#8e44ad",
|
||||
"#2c3e50",
|
||||
"#f1c40f",
|
||||
"#d35400"
|
||||
];
|
||||
|
||||
constructor(
|
||||
app: App,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
onSave: (project: CustomProject) => void
|
||||
) {
|
||||
super(app);
|
||||
this.plugin = plugin;
|
||||
this.onSave = onSave;
|
||||
}
|
||||
|
||||
private addEventListener(element: HTMLElement, event: string, handler: EventListener) {
|
||||
element.addEventListener(event, handler);
|
||||
this.eventListeners.push({ element, event, handler });
|
||||
}
|
||||
|
||||
onOpen() {
|
||||
const { contentEl } = this;
|
||||
|
||||
// Add custom class for styling
|
||||
this.modalEl.addClass("v2-project-modal");
|
||||
|
||||
// Title
|
||||
contentEl.createEl("h2", { text: "Create New Project" });
|
||||
|
||||
// Name input section
|
||||
const nameSection = contentEl.createDiv({ cls: "v2-modal-section" });
|
||||
nameSection.createEl("label", { text: "Project Name" });
|
||||
this.nameInput = nameSection.createEl("input", {
|
||||
cls: "v2-modal-input",
|
||||
attr: {
|
||||
type: "text",
|
||||
placeholder: "Enter project name"
|
||||
}
|
||||
});
|
||||
|
||||
// Color picker section
|
||||
const colorSection = contentEl.createDiv({ cls: "v2-modal-section" });
|
||||
colorSection.createEl("label", { text: "Choose Color" });
|
||||
|
||||
const colorGrid = colorSection.createDiv({ cls: "v2-modal-color-grid" });
|
||||
this.colors.forEach(color => {
|
||||
const colorButton = colorGrid.createDiv({
|
||||
cls: "v2-modal-color-button",
|
||||
attr: { "data-color": color }
|
||||
});
|
||||
colorButton.style.backgroundColor = color;
|
||||
|
||||
if (color === this.selectedColor) {
|
||||
colorButton.addClass("is-selected");
|
||||
}
|
||||
|
||||
this.addEventListener(colorButton, "click", () => {
|
||||
this.selectColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
// Preview section
|
||||
const previewSection = contentEl.createDiv({ cls: "v2-modal-section" });
|
||||
previewSection.createEl("label", { text: "Preview" });
|
||||
const preview = previewSection.createDiv({ cls: "v2-modal-preview" });
|
||||
const previewItem = preview.createDiv({ cls: "v2-project-item-preview" });
|
||||
const previewColor = previewItem.createDiv({ cls: "v2-project-color" });
|
||||
previewColor.style.backgroundColor = this.selectedColor;
|
||||
const previewName = previewItem.createSpan({ cls: "v2-project-name" });
|
||||
|
||||
// Update preview on name change
|
||||
this.addEventListener(this.nameInput, "input", () => {
|
||||
previewName.setText(this.nameInput?.value || "Project Name");
|
||||
});
|
||||
previewName.setText("Project Name");
|
||||
|
||||
// Footer with action buttons
|
||||
const footer = contentEl.createDiv({ cls: "v2-modal-footer" });
|
||||
|
||||
const cancelBtn = footer.createEl("button", {
|
||||
cls: "v2-button v2-button-secondary",
|
||||
text: "Cancel"
|
||||
});
|
||||
this.addEventListener(cancelBtn, "click", () => this.close());
|
||||
|
||||
const createBtn = footer.createEl("button", {
|
||||
cls: "v2-button v2-button-primary",
|
||||
text: "Create Project"
|
||||
});
|
||||
this.addEventListener(createBtn, "click", () => this.save());
|
||||
|
||||
// Handle Enter key
|
||||
this.addEventListener(this.nameInput, "keydown", (e: KeyboardEvent) => {
|
||||
if (e.key === "Enter" && this.nameInput?.value.trim()) {
|
||||
this.save();
|
||||
}
|
||||
});
|
||||
|
||||
// Focus input
|
||||
setTimeout(() => this.nameInput?.focus(), 100);
|
||||
}
|
||||
|
||||
onClose() {
|
||||
// Clean up all event listeners
|
||||
this.eventListeners.forEach(({ element, event, handler }) => {
|
||||
element.removeEventListener(event, handler);
|
||||
});
|
||||
this.eventListeners = [];
|
||||
|
||||
// Clear content
|
||||
const { contentEl } = this;
|
||||
contentEl.empty();
|
||||
}
|
||||
|
||||
private selectColor(color: string) {
|
||||
this.selectedColor = color;
|
||||
|
||||
// Update selected state
|
||||
this.modalEl.querySelectorAll(".v2-modal-color-button").forEach(btn => {
|
||||
btn.removeClass("is-selected");
|
||||
});
|
||||
const selectedBtn = this.modalEl.querySelector(`[data-color="${color}"]`);
|
||||
selectedBtn?.addClass("is-selected");
|
||||
|
||||
// Update preview
|
||||
const previewColor = this.modalEl.querySelector(".v2-project-color") as HTMLElement;
|
||||
if (previewColor) {
|
||||
previewColor.style.backgroundColor = color;
|
||||
}
|
||||
}
|
||||
|
||||
private save() {
|
||||
if (!this.nameInput) return;
|
||||
|
||||
const name = this.nameInput.value.trim();
|
||||
if (!name) {
|
||||
this.nameInput.focus();
|
||||
this.nameInput.addClass("is-error");
|
||||
setTimeout(() => this.nameInput?.removeClass("is-error"), 300);
|
||||
return;
|
||||
}
|
||||
|
||||
const project: CustomProject = {
|
||||
id: `project-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||
name,
|
||||
color: this.selectedColor,
|
||||
createdAt: Date.now(),
|
||||
updatedAt: Date.now()
|
||||
};
|
||||
|
||||
this.onSave(project);
|
||||
this.close();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { setIcon, Menu } from "obsidian";
|
||||
import { Component, setIcon, Menu } from "obsidian";
|
||||
import { WorkspaceSelector } from "./WorkspaceSelector";
|
||||
import { ProjectList } from "@/experimental/v2/components/ProjectList";
|
||||
import { Workspace, V2NavigationItem } from "@/experimental/v2/types";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { t } from "@/translations/helper";
|
||||
|
||||
export class V2Sidebar {
|
||||
export class V2Sidebar extends Component {
|
||||
private containerEl: HTMLElement;
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private workspaceSelector: WorkspaceSelector;
|
||||
|
|
@ -56,11 +56,10 @@ export class V2Sidebar {
|
|||
private onProjectSelect: (projectId: string) => void,
|
||||
collapsed: boolean = false
|
||||
) {
|
||||
super();
|
||||
this.containerEl = containerEl;
|
||||
this.plugin = plugin;
|
||||
this.collapsed = collapsed;
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render() {
|
||||
|
|
@ -188,10 +187,16 @@ export class V2Sidebar {
|
|||
});
|
||||
|
||||
projectHeader.createSpan({ text: "Projects" });
|
||||
const addProjectBtn = projectHeader.createDiv({
|
||||
cls: "v2-add-project-btn",
|
||||
const sortProjectBtn = projectHeader.createDiv({
|
||||
cls: "v2-sort-project-btn",
|
||||
attr: { "aria-label": "Sort projects" }
|
||||
});
|
||||
setIcon(sortProjectBtn, "arrow-up-down");
|
||||
|
||||
// Pass sort button to project list for menu handling
|
||||
sortProjectBtn.addEventListener("click", () => {
|
||||
(this.projectList as any).showSortMenu?.(sortProjectBtn);
|
||||
});
|
||||
setIcon(addProjectBtn, "plus");
|
||||
|
||||
const projectListEl = projectsSection.createDiv();
|
||||
this.projectList = new ProjectList(
|
||||
|
|
@ -199,8 +204,8 @@ export class V2Sidebar {
|
|||
this.plugin,
|
||||
this.onProjectSelect
|
||||
);
|
||||
// Load projects data
|
||||
this.projectList.refresh();
|
||||
// Add ProjectList as a child component
|
||||
this.addChild(this.projectList);
|
||||
|
||||
// Other views section
|
||||
const otherSection = content.createDiv({ cls: "v2-sidebar-section" });
|
||||
|
|
@ -260,6 +265,15 @@ export class V2Sidebar {
|
|||
}
|
||||
}
|
||||
|
||||
onload() {
|
||||
this.render();
|
||||
}
|
||||
|
||||
onunload() {
|
||||
// Clean up is handled by Component base class
|
||||
this.containerEl.empty();
|
||||
}
|
||||
|
||||
public setCollapsed(collapsed: boolean) {
|
||||
this.collapsed = collapsed;
|
||||
this.render();
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@
|
|||
flex: 1;
|
||||
overflow-y: auto;
|
||||
padding: 8px 0;
|
||||
--icon-size: 16px;
|
||||
}
|
||||
|
||||
.v2-sidebar-section {
|
||||
|
|
@ -159,13 +160,15 @@
|
|||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.v2-add-project-btn {
|
||||
.v2-add-project-btn,
|
||||
.v2-sort-project-btn {
|
||||
cursor: pointer;
|
||||
color: var(--text-muted);
|
||||
transition: color 0.15s;
|
||||
}
|
||||
|
||||
.v2-add-project-btn:hover {
|
||||
.v2-add-project-btn:hover,
|
||||
.v2-sort-project-btn:hover {
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@
|
|||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.task-item-container {
|
||||
|
|
|
|||
350
src/styles/v2-project-popover.css
Normal file
350
src/styles/v2-project-popover.css
Normal file
|
|
@ -0,0 +1,350 @@
|
|||
/* Project Popover Styles */
|
||||
.v2-project-popover-container {
|
||||
position: absolute;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
.v2-project-popover {
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
|
||||
min-width: 320px;
|
||||
max-width: 400px;
|
||||
animation: popover-fade-in 0.2s ease-out;
|
||||
}
|
||||
|
||||
@keyframes popover-fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-8px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
.v2-popover-arrow,
|
||||
.v2-popover-arrow::before {
|
||||
position: absolute;
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
background: inherit;
|
||||
}
|
||||
|
||||
.v2-popover-arrow {
|
||||
visibility: hidden;
|
||||
}
|
||||
|
||||
.v2-popover-arrow::before {
|
||||
visibility: visible;
|
||||
content: "";
|
||||
transform: rotate(45deg);
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
/* Arrow positioning for different placements */
|
||||
.v2-project-popover[data-popper-placement^="top"] .v2-popover-arrow {
|
||||
bottom: -5px;
|
||||
}
|
||||
|
||||
.v2-project-popover[data-popper-placement^="top"] .v2-popover-arrow::before {
|
||||
border-top: none;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.v2-project-popover[data-popper-placement^="bottom"] .v2-popover-arrow {
|
||||
top: -5px;
|
||||
}
|
||||
|
||||
.v2-project-popover[data-popper-placement^="bottom"] .v2-popover-arrow::before {
|
||||
border-right: none;
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.v2-project-popover[data-popper-placement^="left"] .v2-popover-arrow {
|
||||
right: -5px;
|
||||
}
|
||||
|
||||
.v2-project-popover[data-popper-placement^="left"] .v2-popover-arrow::before {
|
||||
border-bottom: none;
|
||||
border-left: none;
|
||||
}
|
||||
|
||||
.v2-project-popover[data-popper-placement^="right"] .v2-popover-arrow {
|
||||
left: -5px;
|
||||
}
|
||||
|
||||
.v2-project-popover[data-popper-placement^="right"] .v2-popover-arrow::before {
|
||||
border-top: none;
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.v2-popover-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.v2-popover-header {
|
||||
margin-bottom: 16px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.v2-popover-header h3 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.v2-popover-section {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.v2-popover-section label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.v2-popover-input {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
color: var(--text-normal);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.v2-popover-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
box-shadow: 0 0 0 2px var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.v2-popover-input.is-error {
|
||||
border-color: var(--text-error);
|
||||
animation: shake 0.3s;
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0%,
|
||||
100% {
|
||||
transform: translateX(0);
|
||||
}
|
||||
25% {
|
||||
transform: translateX(-4px);
|
||||
}
|
||||
75% {
|
||||
transform: translateX(4px);
|
||||
}
|
||||
}
|
||||
|
||||
.v2-color-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.v2-color-button {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 2px solid transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.v2-color-button:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.v2-color-button.is-selected {
|
||||
border-color: var(--interactive-accent);
|
||||
box-shadow: 0 0 0 2px var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.v2-color-button.is-selected::after {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
.v2-popover-actions {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 8px;
|
||||
margin-top: 20px;
|
||||
padding-top: 16px;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.v2-button {
|
||||
padding: 6px 16px;
|
||||
border-radius: 4px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: none;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.v2-button-primary {
|
||||
background: var(--interactive-accent);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.v2-button-primary:hover {
|
||||
background: var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.v2-button-secondary {
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.v2-button-secondary:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
/* Modal Styles using Obsidian native Modal */
|
||||
.v2-project-modal {
|
||||
/* Modal will use Obsidian's default styling */
|
||||
}
|
||||
|
||||
.v2-project-modal .modal-content h2 {
|
||||
margin-bottom: 20px;
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.v2-modal-section {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.v2-modal-section label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.v2-modal-input {
|
||||
width: 100%;
|
||||
padding: 10px 14px;
|
||||
background: var(--background-primary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
font-size: 16px;
|
||||
color: var(--text-normal);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.v2-modal-input:focus {
|
||||
outline: none;
|
||||
border-color: var(--interactive-accent);
|
||||
box-shadow: 0 0 0 3px var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.v2-modal-input.is-error {
|
||||
border-color: var(--text-error);
|
||||
animation: shake 0.3s;
|
||||
}
|
||||
|
||||
.v2-modal-color-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(5, 1fr);
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.v2-modal-color-button {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
border-radius: 8px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
border: 3px solid transparent;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.v2-modal-color-button:hover {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.v2-modal-color-button.is-selected {
|
||||
border-color: var(--interactive-accent);
|
||||
box-shadow: 0 0 0 3px var(--interactive-accent-hover);
|
||||
}
|
||||
|
||||
.v2-modal-color-button.is-selected::after {
|
||||
content: "✓";
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
color: white;
|
||||
font-size: 20px;
|
||||
font-weight: bold;
|
||||
text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
|
||||
}
|
||||
|
||||
.v2-modal-preview {
|
||||
padding: 12px;
|
||||
background: var(--background-secondary);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.v2-project-item-preview {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 8px;
|
||||
background: var(--background-primary);
|
||||
border-radius: 6px;
|
||||
}
|
||||
|
||||
.v2-project-item-preview .v2-project-color {
|
||||
width: 8px;
|
||||
height: 32px;
|
||||
border-radius: 2px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.v2-project-item-preview .v2-project-name {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
|
||||
.v2-modal-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
gap: 12px;
|
||||
padding: 20px;
|
||||
border-top: 1px solid var(--background-modifier-border);
|
||||
}
|
||||
|
||||
.v2-modal-footer .v2-button {
|
||||
padding: 10px 24px;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
|
@ -414,7 +414,8 @@
|
|||
display: none;
|
||||
}
|
||||
|
||||
.task-genius-view .task-empty-state {
|
||||
.task-genius-view .task-empty-state,
|
||||
.task-genius-v2-view .task-empty-state {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
flex: 1;
|
||||
|
|
|
|||
309
styles.css
309
styles.css
File diff suppressed because one or more lines are too long
Loading…
Reference in a new issue