mirror of
https://github.com/taskgenius/taskgenius-plugin.git
synced 2026-07-22 06:40:25 +00:00
Merge branch 'master' into fix/cancel-date-goes-before-task-content
This commit is contained in:
commit
8b098bad38
15 changed files with 860 additions and 182 deletions
|
|
@ -4,6 +4,39 @@ All notable changes to beta releases will be documented in this file.
|
|||
|
||||
|
||||
|
||||
## [9.9.0-beta.6](https://github.com/Quorafind/Obsidian-Task-Genius/compare/9.9.0-beta.5...9.9.0-beta.6) (2025-09-27)
|
||||
|
||||
### Features
|
||||
|
||||
* improve date positioning logic for task metadata ([41f1d95](https://github.com/Quorafind/Obsidian-Task-Genius/commit/41f1d95576940fab61d1ded3dfce5dcbaa559420)), closes [#463](https://github.com/Quorafind/Obsidian-Task-Genius/issues/463)
|
||||
* **settings:** add workspace selector component ([27c426c](https://github.com/Quorafind/Obsidian-Task-Genius/commit/27c426c4748afd0470a15913b8c663bcd3cb2cda))
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cancel date goes before task content ([8b48e6a](https://github.com/Quorafind/Obsidian-Task-Genius/commit/8b48e6a25ba04622f359043df7d88e52b38da24f))
|
||||
* **editor:** improve date positioning in task content ([dab3ecb](https://github.com/Quorafind/Obsidian-Task-Genius/commit/dab3ecb5d70cc40c8507bed1c63928e7e8d3eb3b))
|
||||
|
||||
## [9.9.0-beta.5](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/compare/9.9.0-beta.4...9.9.0-beta.5) (2025-09-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **setting:** manage workspace setting should jump to workspace setting tab ([f282bff](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/f282bffbf84d2b8fdde21521620342e1f47f6a58))
|
||||
* **task-mover:** prevent archive markers on non-task lines and preserve folded content ([f20c5eb](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/f20c5ebee3b999fe69a902afee1b0d12e28fcfbe))
|
||||
* **v2:** hide top navigation for two-column views ([2c24068](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/2c24068c69f314d7ccc63212c243e11abaaa6262))
|
||||
|
||||
### Chores
|
||||
|
||||
* **release:** bump version to 9.9.0-beta.5 [beta] ([ef0834c](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/ef0834c65a859eba1b8fbe68cf44d8328d2a7a9e))
|
||||
* **version:** fix version issue before ([8abd736](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/8abd736e8400b97657fd63f0857d927cbc71e3e8))
|
||||
|
||||
## [9.9.0-beta.5](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/compare/9.9.0-beta.4...9.9.0-beta.5) (2025-09-26)
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **setting:** manage workspace setting should jump to workspace setting tab ([f282bff](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/f282bffbf84d2b8fdde21521620342e1f47f6a58))
|
||||
* **task-mover:** prevent archive markers on non-task lines and preserve folded content ([f20c5eb](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/f20c5ebee3b999fe69a902afee1b0d12e28fcfbe))
|
||||
* **v2:** hide top navigation for two-column views ([2c24068](https://github.com/Quorafind/Obsidian-Task-Progress-Bar/commit/2c24068c69f314d7ccc63212c243e11abaaa6262))
|
||||
|
||||
## [9.9.0-beta.4](https://github.com/Quorafind/Obsidian-Task-Genius/compare/9.9.0-beta.3...9.9.0-beta.4) (2025-09-24)
|
||||
|
||||
### Features
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "obsidian-task-progress-bar",
|
||||
"name": "Task Genius",
|
||||
"version": "9.9.0-beta.4",
|
||||
"version": "9.9.0-beta.6",
|
||||
"minAppVersion": "0.15.2",
|
||||
"description": "Comprehensive task management that includes progress bars, task status cycling, and advanced task tracking features.",
|
||||
"author": "Boninall",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "task-genius",
|
||||
"version": "9.9.0-beta.4",
|
||||
"version": "9.9.0-beta.6",
|
||||
"description": "Comprehensive task management plugin for Obsidian with progress bars, task status cycling, and advanced task tracking features.",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
215
src/__tests__/autoDateManager.improved.test.ts
Normal file
215
src/__tests__/autoDateManager.improved.test.ts
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// @ts-ignore
|
||||
import { describe, it, expect, beforeEach, jest } from "@jest/globals";
|
||||
import {
|
||||
findMetadataInsertPosition,
|
||||
} from "../editor-extensions/date-time/date-manager";
|
||||
import TaskProgressBarPlugin from "../index";
|
||||
import { App } from "obsidian";
|
||||
|
||||
// Mock the plugin
|
||||
const mockPlugin: Partial<TaskProgressBarPlugin> = {
|
||||
settings: {
|
||||
autoDateManager: {
|
||||
enabled: true,
|
||||
manageStartDate: true,
|
||||
manageCompletedDate: true,
|
||||
manageCancelledDate: true,
|
||||
startDateFormat: "YYYY-MM-DD",
|
||||
completedDateFormat: "YYYY-MM-DD",
|
||||
cancelledDateFormat: "YYYY-MM-DD",
|
||||
startDateMarker: "🚀",
|
||||
completedDateMarker: "✅",
|
||||
cancelledDateMarker: "❌",
|
||||
},
|
||||
preferMetadataFormat: "emoji",
|
||||
taskStatuses: {
|
||||
completed: "x|X",
|
||||
inProgress: "/|-",
|
||||
abandoned: "_",
|
||||
planned: "!",
|
||||
notStarted: " ",
|
||||
},
|
||||
},
|
||||
} as unknown as TaskProgressBarPlugin;
|
||||
|
||||
describe("Improved Date Insertion Logic", () => {
|
||||
describe("Content with Special Characters", () => {
|
||||
it("should handle task with wiki links correctly", () => {
|
||||
const lineText = "- [ ] Check [[Project Notes]] for details";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Check [[Project Notes]] for details");
|
||||
});
|
||||
|
||||
it("should handle nested wiki links", () => {
|
||||
const lineText = "- [ ] Read [[Books/[[Nested]] Guide]]";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Read [[Books/[[Nested]] Guide]]");
|
||||
});
|
||||
|
||||
it("should handle hashtag in URL content", () => {
|
||||
const lineText = "- [ ] Visit https://example.com/#section";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Visit https://example.com/#section");
|
||||
});
|
||||
|
||||
it("should handle emoji in task content", () => {
|
||||
const lineText = "- [ ] Fix the 🚀 rocket launch code";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Fix the 🚀 rocket launch code");
|
||||
});
|
||||
|
||||
it("should handle multiple hashtags in URL", () => {
|
||||
const lineText = "- [ ] Check site.com/#anchor#section#part";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Check site.com/#anchor#section#part");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Metadata Positioning", () => {
|
||||
it("should insert cancelled date before tags", () => {
|
||||
const lineText = "- [ ] Important task #urgent #priority";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Important task");
|
||||
expect(lineText.substring(position)).toBe(" #urgent #priority");
|
||||
});
|
||||
|
||||
it("should insert cancelled date before dataview fields", () => {
|
||||
const lineText = "- [ ] Task content [due:: 2025-10-01]";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Task content");
|
||||
expect(lineText.substring(position)).toBe(" [due:: 2025-10-01]");
|
||||
});
|
||||
|
||||
it("should insert cancelled date after start date", () => {
|
||||
const lineText = "- [ ] Task 🚀 2025-09-01";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
// For cancelled date, it should go after the start date
|
||||
expect(position).toBeGreaterThan(24); // After "🚀 2025-09-01"
|
||||
});
|
||||
|
||||
it("should handle multiple metadata items correctly", () => {
|
||||
const lineText = "- [ ] Task content #tag1 [due:: 2025-10-01] #tag2";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Task content");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Block References", () => {
|
||||
it("should insert date before block reference", () => {
|
||||
const lineText = "- [ ] Task with reference ^task-123";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"completed"
|
||||
);
|
||||
// For completed date, should be before block reference
|
||||
expect(lineText.substring(position)).toContain("^task-123");
|
||||
});
|
||||
|
||||
it("should handle block reference with trailing spaces", () => {
|
||||
const lineText = "- [ ] Task with reference ^task-123 ";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"completed"
|
||||
);
|
||||
expect(lineText.substring(position)).toContain("^task-123");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Edge Cases", () => {
|
||||
it("should handle empty task", () => {
|
||||
const lineText = "- [ ] ";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(position).toBe(6); // Right after "- [ ] "
|
||||
});
|
||||
|
||||
it("should handle task with only spaces", () => {
|
||||
const lineText = "- [ ] ";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(position).toBe(6); // After trimming trailing spaces
|
||||
});
|
||||
|
||||
it("should handle task with brackets in content", () => {
|
||||
const lineText = "- [ ] Use array[0] or dict[key] in code";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Use array[0] or dict[key] in code");
|
||||
});
|
||||
|
||||
it("should not confuse markdown links with wiki links", () => {
|
||||
const lineText = "- [ ] Check [this link](https://example.com)";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
expect(lineText.substring(0, position)).toBe("- [ ] Check [this link](https://example.com)");
|
||||
});
|
||||
});
|
||||
|
||||
describe("Original PR Issue", () => {
|
||||
it("should place cancelled date after task content, not before", () => {
|
||||
const lineText = "- [ ] test entry";
|
||||
const position = findMetadataInsertPosition(
|
||||
lineText,
|
||||
mockPlugin as TaskProgressBarPlugin,
|
||||
"cancelled"
|
||||
);
|
||||
// Position should be after "test entry", not after "- [ ] "
|
||||
expect(position).toBe(16); // After "test entry"
|
||||
|
||||
// Simulate adding the cancelled date
|
||||
const result = lineText.slice(0, position) + " ❌2025-09-25" + lineText.slice(position);
|
||||
expect(result).toBe("- [ ] test entry ❌2025-09-25");
|
||||
expect(result).not.toBe("- [ ] ❌2025-09-25test entry"); // This was the bug
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -502,6 +502,18 @@ export class TaskUtils {
|
|||
|
||||
// Get the current task line
|
||||
const currentLine = lines[taskLine];
|
||||
|
||||
// Check if the current line is actually a task
|
||||
// Tasks must match pattern: optional whitespace + list marker (-, number., or *) + space + checkbox
|
||||
const taskPattern = /^\s*(-|\d+\.|\*) \[(.)\]/;
|
||||
if (!taskPattern.test(currentLine)) {
|
||||
// Not a task line, return empty result
|
||||
return {
|
||||
content: "",
|
||||
linesToRemove: [],
|
||||
};
|
||||
}
|
||||
|
||||
const currentIndent = this.getIndentation(currentLine, app);
|
||||
|
||||
// Extract the parent task's mark
|
||||
|
|
@ -539,9 +551,42 @@ export class TaskUtils {
|
|||
// Include the current line and completed child tasks
|
||||
resultLines.push(parentTaskWithMarker);
|
||||
|
||||
// First, collect all indented content that belongs to this task (folded content)
|
||||
// This includes notes, tags, and other content that is indented under the task
|
||||
const taskContent: { line: string; index: number; indent: number }[] = [];
|
||||
for (let i = taskLine + 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const lineIndent = this.getIndentation(line, app);
|
||||
|
||||
// Stop if we've reached content at the same or lower indentation level
|
||||
if (lineIndent <= currentIndent) {
|
||||
break;
|
||||
}
|
||||
|
||||
// Check if this is a task at the direct child level
|
||||
const isTask = /^\s*(-|\d+\.|\*) \[(.)\]/.test(line);
|
||||
if (isTask) {
|
||||
// For non-"all" modes, we need to handle child tasks specially
|
||||
// So we stop collecting the immediate folded content here
|
||||
if (moveMode !== "all") {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// This is indented content that belongs to the parent task
|
||||
taskContent.push({ line, index: i, indent: lineIndent });
|
||||
}
|
||||
|
||||
// If we're moving all subtasks, we'll collect them all
|
||||
if (moveMode === "all") {
|
||||
for (let i = taskLine + 1; i < lines.length; i++) {
|
||||
// Add all the folded content and subtasks
|
||||
for (const item of taskContent) {
|
||||
resultLines.push(this.completeTaskIfNeeded(item.line, settings));
|
||||
linesToRemove.push(item.index);
|
||||
}
|
||||
|
||||
// Continue collecting all nested subtasks beyond the immediate folded content
|
||||
for (let i = taskLine + taskContent.length + 1; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const lineIndent = this.getIndentation(line, app);
|
||||
|
||||
|
|
@ -559,6 +604,11 @@ export class TaskUtils {
|
|||
}
|
||||
// If we're moving only completed tasks or direct children
|
||||
else {
|
||||
// Always include the immediate folded content (notes, tags, etc.)
|
||||
for (const item of taskContent) {
|
||||
resultLines.push(item.line); // Don't complete non-task content
|
||||
linesToRemove.push(item.index);
|
||||
}
|
||||
// First pass: collect all child tasks to analyze
|
||||
const childTasks: {
|
||||
line: string;
|
||||
|
|
@ -568,7 +618,9 @@ export class TaskUtils {
|
|||
isIncompleted: boolean;
|
||||
}[] = [];
|
||||
|
||||
for (let i = taskLine + 1; i < lines.length; i++) {
|
||||
// Start after the folded content we already collected
|
||||
const startIndex = taskLine + taskContent.length + 1;
|
||||
for (let i = startIndex; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
const lineIndent = this.getIndentation(line, app);
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { SettingsIndexer } from "@/components/features/settings/core/SettingsInd
|
|||
import { SearchResult } from "@/types/SettingsSearch";
|
||||
import { t } from "@/translations/helper";
|
||||
import { TaskProgressBarSettingTab } from "@/setting";
|
||||
import { WorkspaceSettingsSelector } from "./WorkspaceSettingsSelector";
|
||||
|
||||
/**
|
||||
* 设置搜索组件
|
||||
|
|
@ -21,6 +22,7 @@ export class SettingsSearchComponent extends Component {
|
|||
private debouncedSearch: (query: string) => void;
|
||||
private isVisible = false;
|
||||
private blurTimeoutId = 0;
|
||||
private workspaceSelector: WorkspaceSettingsSelector | null = null;
|
||||
|
||||
constructor(
|
||||
settingTab: TaskProgressBarSettingTab,
|
||||
|
|
@ -46,8 +48,29 @@ export class SettingsSearchComponent extends Component {
|
|||
* 创建搜索界面
|
||||
*/
|
||||
private createSearchUI(): void {
|
||||
// 创建主容器
|
||||
const mainContainer = this.containerEl.createDiv();
|
||||
mainContainer.addClass("tg-settings-main-container");
|
||||
|
||||
// 创建头部栏(包含workspace选择器和搜索框)
|
||||
const headerBar = mainContainer.createDiv();
|
||||
headerBar.addClass("tg-settings-header-bar");
|
||||
|
||||
// 创建workspace选择器容器
|
||||
const workspaceSelectorContainer = headerBar.createDiv();
|
||||
workspaceSelectorContainer.addClass("tg-workspace-selector-container");
|
||||
|
||||
// 初始化workspace选择器
|
||||
if (this.settingTab.plugin.workspaceManager) {
|
||||
this.workspaceSelector = new WorkspaceSettingsSelector(
|
||||
workspaceSelectorContainer,
|
||||
this.settingTab.plugin,
|
||||
this.settingTab
|
||||
);
|
||||
}
|
||||
|
||||
// 创建搜索容器
|
||||
const searchContainer = this.containerEl.createDiv();
|
||||
const searchContainer = headerBar.createDiv();
|
||||
searchContainer.addClass("tg-settings-search-container");
|
||||
|
||||
// 创建搜索输入框容器
|
||||
|
|
@ -75,8 +98,8 @@ export class SettingsSearchComponent extends Component {
|
|||
setIcon(this.clearButton, "x");
|
||||
this.clearButton.style.display = "none";
|
||||
|
||||
// 创建搜索结果容器
|
||||
this.resultsContainerEl = searchContainer.createDiv();
|
||||
// 创建搜索结果容器(在header bar外面)
|
||||
this.resultsContainerEl = mainContainer.createDiv();
|
||||
this.resultsContainerEl.addClass("tg-settings-search-results");
|
||||
this.resultsContainerEl.style.display = "none";
|
||||
this.resultsContainerEl.setAttribute("role", "listbox");
|
||||
|
|
|
|||
|
|
@ -0,0 +1,148 @@
|
|||
import { Menu, setIcon } from "obsidian";
|
||||
import type TaskProgressBarPlugin from "@/index";
|
||||
import { WorkspaceData } from "@/experimental/v2/types/workspace";
|
||||
import { t } from "@/translations/helper";
|
||||
import { TaskProgressBarSettingTab } from "@/setting";
|
||||
|
||||
export class WorkspaceSettingsSelector {
|
||||
private containerEl: HTMLElement;
|
||||
private plugin: TaskProgressBarPlugin;
|
||||
private settingTab: TaskProgressBarSettingTab;
|
||||
private currentWorkspaceId: string;
|
||||
private buttonEl: HTMLElement;
|
||||
|
||||
constructor(
|
||||
containerEl: HTMLElement,
|
||||
plugin: TaskProgressBarPlugin,
|
||||
settingTab: TaskProgressBarSettingTab
|
||||
) {
|
||||
this.containerEl = containerEl;
|
||||
this.plugin = plugin;
|
||||
this.settingTab = settingTab;
|
||||
this.currentWorkspaceId =
|
||||
plugin.workspaceManager?.getActiveWorkspace().id || "";
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
private render() {
|
||||
// Create workspace selector container
|
||||
const selectorContainer = this.containerEl.createDiv({
|
||||
cls: "workspace-settings-selector",
|
||||
});
|
||||
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
const currentWorkspace =
|
||||
this.plugin.workspaceManager.getActiveWorkspace();
|
||||
|
||||
this.buttonEl = selectorContainer.createDiv({
|
||||
cls: "workspace-settings-selector-button",
|
||||
});
|
||||
|
||||
// Workspace icon
|
||||
const workspaceIcon = this.buttonEl.createDiv({
|
||||
cls: "workspace-icon",
|
||||
});
|
||||
setIcon(workspaceIcon, currentWorkspace.icon || "layers");
|
||||
|
||||
// Workspace name
|
||||
const workspaceName = this.buttonEl.createSpan({
|
||||
cls: "workspace-name",
|
||||
text: currentWorkspace.name,
|
||||
});
|
||||
|
||||
// Dropdown arrow
|
||||
const dropdownIcon = this.buttonEl.createDiv({
|
||||
cls: "workspace-dropdown-icon",
|
||||
});
|
||||
setIcon(dropdownIcon, "chevron-down");
|
||||
|
||||
// Click handler
|
||||
this.buttonEl.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
this.showWorkspaceMenu(e);
|
||||
});
|
||||
}
|
||||
|
||||
private showWorkspaceMenu(event: MouseEvent) {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
const menu = new Menu();
|
||||
const workspaces = this.plugin.workspaceManager.getAllWorkspaces();
|
||||
const currentWorkspace =
|
||||
this.plugin.workspaceManager.getActiveWorkspace();
|
||||
|
||||
// Add workspace items for switching
|
||||
workspaces.forEach((workspace) => {
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(workspace.name)
|
||||
.setIcon(workspace.icon || "layers")
|
||||
.onClick(async () => {
|
||||
await this.switchWorkspace(workspace.id);
|
||||
});
|
||||
|
||||
// Mark current workspace with checkmark
|
||||
if (workspace.id === currentWorkspace.id) {
|
||||
item.setChecked(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add separator
|
||||
menu.addSeparator();
|
||||
|
||||
// Add "Manage Workspaces..." option to navigate to settings
|
||||
menu.addItem((item) => {
|
||||
item.setTitle(t("Manage Workspaces..."))
|
||||
.setIcon("settings")
|
||||
.onClick(() => {
|
||||
// Navigate to workspace settings tab
|
||||
this.settingTab.switchToTab("workspaces");
|
||||
});
|
||||
});
|
||||
|
||||
menu.showAtMouseEvent(event);
|
||||
}
|
||||
|
||||
private async switchWorkspace(workspaceId: string) {
|
||||
if (!this.plugin.workspaceManager) return;
|
||||
|
||||
// Switch workspace
|
||||
await this.plugin.workspaceManager.setActiveWorkspace(workspaceId);
|
||||
this.currentWorkspaceId = workspaceId;
|
||||
|
||||
// Update button display
|
||||
this.updateDisplay();
|
||||
|
||||
// Trigger settings reload to reflect workspace change
|
||||
await this.plugin.saveSettings();
|
||||
this.settingTab.applySettingsUpdate();
|
||||
}
|
||||
|
||||
private updateDisplay() {
|
||||
if (!this.plugin.workspaceManager || !this.buttonEl) return;
|
||||
|
||||
const currentWorkspace =
|
||||
this.plugin.workspaceManager.getActiveWorkspace();
|
||||
|
||||
// Update icon
|
||||
const iconEl = this.buttonEl.querySelector(".workspace-icon");
|
||||
if (iconEl) {
|
||||
iconEl.empty();
|
||||
setIcon(iconEl as HTMLElement, currentWorkspace.icon || "layers");
|
||||
}
|
||||
|
||||
// Update name
|
||||
const nameEl = this.buttonEl.querySelector(".workspace-name");
|
||||
if (nameEl) {
|
||||
nameEl.textContent = currentWorkspace.name;
|
||||
}
|
||||
}
|
||||
|
||||
public setWorkspace(workspaceId: string) {
|
||||
this.currentWorkspaceId = workspaceId;
|
||||
this.updateDisplay();
|
||||
}
|
||||
}
|
||||
|
|
@ -670,25 +670,94 @@ function findMetadataInsertPosition(
|
|||
): number {
|
||||
// Work with the full line text, don't extract block reference yet
|
||||
const blockRef = detectBlockReference(lineText);
|
||||
|
||||
// Find the end of the task content, right after the task description
|
||||
const taskMatch = lineText.match(
|
||||
/^[\s|\t]*([-*+]|\d+\.)\s\[(.)\]\s*([^#\[📅🚀✅❌🛫▶️⏰🏁]*)/
|
||||
);
|
||||
// Find the task marker and status
|
||||
const taskMatch = lineText.match(/^[\s|\t]*([-*+]|\d+\.)\s\[(.)\]\s*/);
|
||||
if (!taskMatch) return blockRef ? blockRef.index : lineText.length;
|
||||
|
||||
// Start position is right after the task checkbox
|
||||
let position = taskMatch[0].length;
|
||||
|
||||
// For cancelled date, we need special handling to insert after all metadata and start dates
|
||||
// Find the actual end of task content by scanning through the text
|
||||
// This handles content with special characters, links, etc.
|
||||
let contentEnd = position;
|
||||
let inLink = 0; // Track nested [[links]]
|
||||
let inDataview = false; // Track [field:: value] metadata
|
||||
const remainingText = lineText.slice(position);
|
||||
|
||||
for (let i = 0; i < remainingText.length; i++) {
|
||||
const char = remainingText[i];
|
||||
const nextChar = remainingText[i + 1];
|
||||
const twoChars = char + (nextChar || '');
|
||||
|
||||
// Handle [[wiki links]] - they are part of content
|
||||
if (twoChars === '[[') {
|
||||
inLink++;
|
||||
contentEnd = position + i + 2;
|
||||
i++; // Skip next char
|
||||
continue;
|
||||
}
|
||||
if (twoChars === ']]' && inLink > 0) {
|
||||
inLink--;
|
||||
contentEnd = position + i + 2;
|
||||
i++; // Skip next char
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we're inside a link, everything is content
|
||||
if (inLink > 0) {
|
||||
contentEnd = position + i + 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check for dataview metadata [field:: value]
|
||||
if (char === '[' && !inDataview) {
|
||||
const afterBracket = remainingText.slice(i + 1);
|
||||
if (afterBracket.match(/^[a-zA-Z]+::/)) {
|
||||
// This is dataview metadata, stop here
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Check for tags (only if preceded by whitespace or at start)
|
||||
if (char === '#') {
|
||||
if (i === 0 || remainingText[i - 1] === ' ' || remainingText[i - 1] === '\t') {
|
||||
// Check if this is actually a tag (followed by word characters)
|
||||
const afterHash = remainingText.slice(i + 1);
|
||||
if (afterHash.match(/^[\w-]+/)) {
|
||||
// This is a tag, stop here
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for date emojis (these are metadata markers)
|
||||
const dateEmojis = ['📅', '🚀', '✅', '❌', '🛫', '▶️', '⏰', '🏁'];
|
||||
if (dateEmojis.includes(char)) {
|
||||
// Check if this is followed by a date pattern
|
||||
const afterEmoji = remainingText.slice(i + 1);
|
||||
if (afterEmoji.match(/^\s*\d{4}-\d{2}-\d{2}/)) {
|
||||
// This is a date marker, stop here
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Regular content character
|
||||
contentEnd = position + i + 1;
|
||||
}
|
||||
|
||||
position = contentEnd;
|
||||
|
||||
// Trim trailing whitespace
|
||||
while (position > taskMatch[0].length && lineText[position - 1] === ' ') {
|
||||
position--;
|
||||
}
|
||||
|
||||
// For cancelled date, we need special handling to insert after start dates if present
|
||||
if (dateType === "cancelled") {
|
||||
const useDataviewFormat =
|
||||
plugin.settings.preferMetadataFormat === "dataview";
|
||||
|
||||
// Find the last occurrence of either:
|
||||
// 1. Start date marker (🛫 or [start::)
|
||||
// 2. If no start date, find the end of all metadata
|
||||
|
||||
// Look for start date first
|
||||
// Look for existing start date
|
||||
let startDateFound = false;
|
||||
if (useDataviewFormat) {
|
||||
const startDateMatch = lineText.match(/\[start::[^\]]*\]/);
|
||||
|
|
@ -717,9 +786,6 @@ function findMetadataInsertPosition(
|
|||
);
|
||||
startDateMatch = lineText.match(pattern);
|
||||
if (startDateMatch) {
|
||||
console.log(
|
||||
`[AutoDateManager] Found start date with emoji ${emoji}`
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
@ -728,47 +794,11 @@ function findMetadataInsertPosition(
|
|||
if (startDateMatch && startDateMatch.index !== undefined) {
|
||||
position = startDateMatch.index + startDateMatch[0].length;
|
||||
startDateFound = true;
|
||||
console.log(
|
||||
`[AutoDateManager] Found start date at index ${startDateMatch.index}, length ${startDateMatch[0].length}, new position: ${position}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[AutoDateManager] Start date found: ${startDateFound}, position: ${position}`
|
||||
);
|
||||
|
||||
if (!startDateFound) {
|
||||
// No start date found, find the end of all metadata
|
||||
// This includes tags (#), dataview fields ([field::]), and other date markers
|
||||
let lastMetadataEnd = position;
|
||||
|
||||
// Find all metadata occurrences
|
||||
const metadataRegexes = [
|
||||
/#[\w-]+/g, // Tags
|
||||
/\[[a-zA-Z]+::[^\]]*\]/g, // Dataview fields
|
||||
/[📅🚀✅❌🛫▶️⏰🏁]\s*\d{4}-\d{2}-\d{2}(?:\s+\d{2}:\d{2}(?::\d{2})?)?/g, // Date markers (including all common start emojis)
|
||||
];
|
||||
|
||||
for (const regex of metadataRegexes) {
|
||||
let match;
|
||||
while ((match = regex.exec(lineText)) !== null) {
|
||||
if (match.index >= position) {
|
||||
const matchEnd = match.index + match[0].length;
|
||||
if (matchEnd > lastMetadataEnd) {
|
||||
lastMetadataEnd = matchEnd;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
position = lastMetadataEnd;
|
||||
}
|
||||
|
||||
// Ensure we have a space before the cancelled date
|
||||
if (position > 0 && lineText[position - 1] !== " ") {
|
||||
// No need to add space here, it will be added with the date
|
||||
}
|
||||
// If no start date found, position is already correct from initial parsing
|
||||
// It points to the end of content before metadata
|
||||
} else if (dateType === "completed") {
|
||||
// For completed date, we want to go to the end of the line (before block reference)
|
||||
// This is different from cancelled/start dates which go after content/metadata
|
||||
|
|
@ -783,56 +813,9 @@ function findMetadataInsertPosition(
|
|||
}
|
||||
}
|
||||
} else {
|
||||
// For start date, find the end of main content before metadata
|
||||
let contentEnd = position;
|
||||
let inBrackets = false;
|
||||
const chars = lineText.slice(position).split("");
|
||||
|
||||
for (let i = 0; i < chars.length; i++) {
|
||||
const char = chars[i];
|
||||
|
||||
// Track if we're inside dataview brackets
|
||||
if (char === "[" && !inBrackets) {
|
||||
// Check if this is a dataview field like [due::...]
|
||||
const remainingText = lineText.slice(position + i);
|
||||
if (remainingText.match(/^\[[a-zA-Z]+::/)) {
|
||||
// This is metadata, stop here
|
||||
break;
|
||||
}
|
||||
inBrackets = true;
|
||||
contentEnd = position + i + 1;
|
||||
} else if (char === "]" && inBrackets) {
|
||||
inBrackets = false;
|
||||
contentEnd = position + i + 1;
|
||||
} else if (!inBrackets) {
|
||||
// Check for metadata markers when not inside brackets
|
||||
if (
|
||||
char === "#" ||
|
||||
char === "📅" ||
|
||||
char === "🚀" ||
|
||||
char === "✅" ||
|
||||
char === "❌" ||
|
||||
char === "🛫" ||
|
||||
char === "▶️" ||
|
||||
char === "⏰" ||
|
||||
char === "🏁"
|
||||
) {
|
||||
// This is metadata, stop here
|
||||
break;
|
||||
}
|
||||
contentEnd = position + i + 1;
|
||||
} else {
|
||||
// Inside brackets, keep going
|
||||
contentEnd = position + i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
position = contentEnd;
|
||||
|
||||
// Trim any trailing whitespace from position
|
||||
while (position > 0 && lineText[position - 1] === " ") {
|
||||
position--;
|
||||
}
|
||||
// For start date, the position has already been calculated correctly
|
||||
// in the initial content parsing above
|
||||
// No additional processing needed
|
||||
}
|
||||
|
||||
// Ensure position doesn't exceed the block reference position
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
import {
|
||||
ItemView,
|
||||
WorkspaceLeaf,
|
||||
Plugin,
|
||||
Notice,
|
||||
debounce,
|
||||
setIcon,
|
||||
|
|
@ -9,8 +8,8 @@ import {
|
|||
TFile,
|
||||
ButtonComponent,
|
||||
} from "obsidian";
|
||||
import TaskProgressBarPlugin from "../../index";
|
||||
import { Task, BaseTask } from "../../types/task";
|
||||
import TaskProgressBarPlugin from "@/index";
|
||||
import { Task } from "@/types/task";
|
||||
import { V2Sidebar } from "./components/V2Sidebar";
|
||||
import "./styles/v2.css";
|
||||
import "./styles/v2-enhanced.css";
|
||||
|
|
@ -22,43 +21,42 @@ import {
|
|||
onWorkspaceSwitched,
|
||||
onWorkspaceOverridesSaved,
|
||||
} from "./events/ui-event";
|
||||
import { Events, on } from "../../dataflow/events/Events";
|
||||
import { TaskListItemComponent } from "../../components/features/task/view/listItem";
|
||||
import { TaskTreeItemComponent } from "../../components/features/task/view/treeItem";
|
||||
import { Events, on } from "@/dataflow/events/Events";
|
||||
import { TaskListItemComponent } from "@/components/features/task/view/listItem";
|
||||
import { TaskTreeItemComponent } from "@/components/features/task/view/treeItem";
|
||||
import {
|
||||
CalendarComponent,
|
||||
CalendarEvent,
|
||||
} from "../../components/features/calendar";
|
||||
import { KanbanComponent } from "../../components/features/kanban/kanban";
|
||||
import { GanttComponent } from "../../components/features/gantt/gantt";
|
||||
import { filterTasks } from "../../utils/task/task-filter-utils";
|
||||
} from "@/components/features/calendar";
|
||||
import { KanbanComponent } from "@/components/features/kanban/kanban";
|
||||
import { GanttComponent } from "@/components/features/gantt/gantt";
|
||||
import { filterTasks } from "@/utils/task/task-filter-utils";
|
||||
import {
|
||||
getViewSettingOrDefault,
|
||||
TwoColumnSpecificConfig,
|
||||
} from "../../common/setting-definition";
|
||||
import { ContentComponent } from "../../components/features/task/view/content";
|
||||
import { ForecastComponent } from "../../components/features/task/view/forecast";
|
||||
import { TagsComponent } from "../../components/features/task/view/tags";
|
||||
import { ProjectsComponent } from "../../components/features/task/view/projects";
|
||||
import { ReviewComponent } from "../../components/features/task/view/review";
|
||||
import { Habit } from "../../components/features/habit/habit";
|
||||
import { ViewComponentManager } from "../../components/ui/behavior/ViewComponentManager";
|
||||
import { TaskPropertyTwoColumnView } from "../../components/features/task/view/TaskPropertyTwoColumnView";
|
||||
} from "@/common/setting-definition";
|
||||
import { ContentComponent } from "@/components/features/task/view/content";
|
||||
import { ForecastComponent } from "@/components/features/task/view/forecast";
|
||||
import { TagsComponent } from "@/components/features/task/view/tags";
|
||||
import { ProjectsComponent } from "@/components/features/task/view/projects";
|
||||
import { ReviewComponent } from "@/components/features/task/view/review";
|
||||
import { Habit } from "@/components/features/habit/habit";
|
||||
import { ViewComponentManager } from "@/components/ui/behavior/ViewComponentManager";
|
||||
import { TaskPropertyTwoColumnView } from "@/components/features/task/view/TaskPropertyTwoColumnView";
|
||||
import {
|
||||
TaskDetailsComponent,
|
||||
createTaskCheckbox,
|
||||
} from "../../components/features/task/view/details";
|
||||
import { ConfirmModal } from "../../components/ui/modals/ConfirmModal";
|
||||
import { QuickCaptureModal } from "../../components/features/quick-capture/modals/QuickCaptureModal";
|
||||
} from "@/components/features/task/view/details";
|
||||
import { ConfirmModal } from "@/components/ui/modals/ConfirmModal";
|
||||
import { QuickCaptureModal } from "@/components/features/quick-capture/modals/QuickCaptureModal";
|
||||
import {
|
||||
ViewTaskFilterPopover,
|
||||
ViewTaskFilterModal,
|
||||
} from "../../components/features/task/filter";
|
||||
import { RootFilterState } from "../../components/features/task/filter/ViewTaskFilter";
|
||||
} from "@/components/features/task/filter";
|
||||
import { RootFilterState } from "@/components/features/task/filter/ViewTaskFilter";
|
||||
import { Platform } from "obsidian";
|
||||
import { TaskProgressBarSettingTab } from "../../setting";
|
||||
import { isDataflowEnabled } from "../../dataflow/createDataflow";
|
||||
import { t } from "../../translations/helper";
|
||||
import { isDataflowEnabled } from "@/dataflow/createDataflow";
|
||||
import { t } from "@/translations/helper";
|
||||
|
||||
export const TASK_VIEW_V2_TYPE = "task-genius-view-v2";
|
||||
|
||||
|
|
@ -121,27 +119,27 @@ export class TaskViewV2 extends ItemView {
|
|||
private taskCountEl: HTMLElement;
|
||||
private filterInputEl: HTMLInputElement;
|
||||
private viewToggleBtn: HTMLElement;
|
||||
private isTreeView: boolean = false;
|
||||
private isTreeView = false;
|
||||
|
||||
// View action buttons
|
||||
private detailsToggleBtn: HTMLElement;
|
||||
private currentSelectedTaskId: string | null = null;
|
||||
private lastToggleTimestamp: number = 0;
|
||||
private isDetailsVisible: boolean = false;
|
||||
private lastToggleTimestamp = 0;
|
||||
private isDetailsVisible = false;
|
||||
private currentFilterState: RootFilterState | null = null;
|
||||
|
||||
// Sidebar collapse state
|
||||
private isSidebarCollapsed: boolean = false;
|
||||
private isSidebarCollapsed = false;
|
||||
private sidebarToggleBtn: HTMLElement | null = null;
|
||||
private isMobileDrawerOpen: boolean = false;
|
||||
private isMobileDrawerOpen = false;
|
||||
private drawerOverlay: HTMLElement | null = null;
|
||||
|
||||
// Touch gesture tracking
|
||||
private touchStartX: number = 0;
|
||||
private touchStartY: number = 0;
|
||||
private touchCurrentX: number = 0;
|
||||
private isSwiping: boolean = false;
|
||||
private swipeThreshold: number = 50;
|
||||
private touchStartX = 0;
|
||||
private touchStartY = 0;
|
||||
private touchCurrentX = 0;
|
||||
private isSwiping = false;
|
||||
private swipeThreshold = 50;
|
||||
|
||||
// V2 Details panel
|
||||
private detailsPanelEl: HTMLElement | null = null;
|
||||
|
|
@ -831,7 +829,7 @@ export class TaskViewV2 extends ItemView {
|
|||
}
|
||||
}
|
||||
|
||||
private hideAllComponents(forceHideAll: boolean = false) {
|
||||
private hideAllComponents(forceHideAll = false) {
|
||||
// During initialization, we need to hide all components initially
|
||||
// But during view switches, we can be smarter about it
|
||||
const isInitialHide = this.isInitializing && forceHideAll;
|
||||
|
|
@ -993,7 +991,7 @@ export class TaskViewV2 extends ItemView {
|
|||
);
|
||||
}
|
||||
|
||||
private async loadTasks(showLoading: boolean = true) {
|
||||
private async loadTasks(showLoading = true) {
|
||||
try {
|
||||
console.log("[TG-V2] loadTasks started, showLoading:", showLoading);
|
||||
// Only show loading state if requested, not initializing, and we don't have tasks
|
||||
|
|
@ -3056,8 +3054,8 @@ export class TaskViewV2 extends ItemView {
|
|||
// Check for special two-column views
|
||||
const viewConfig = getViewSettingOrDefault(this.plugin, viewId as any);
|
||||
if (viewConfig?.specificConfig?.viewType === "twocolumn") {
|
||||
// Two-column views typically support list and tree modes
|
||||
return ["list", "tree"];
|
||||
// Two-column views have their own specialized UI and don't need view mode switching
|
||||
return [];
|
||||
}
|
||||
|
||||
// Check for special views managed by ViewComponentManager
|
||||
|
|
|
|||
|
|
@ -184,6 +184,12 @@ export class WorkspaceSelector {
|
|||
this.plugin.app.setting.openTabById(
|
||||
"obsidian-task-progress-bar"
|
||||
);
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.plugin.settingTab) {
|
||||
this.plugin.settingTab.openTab("workspaces");
|
||||
}
|
||||
}, 100);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ import "./styles/setting-v2.css";
|
|||
import "./styles/beta-warning.css";
|
||||
import "./styles/settings-search.css";
|
||||
import "./styles/settings-migration.css";
|
||||
import "./styles/workspace-settings-selector.css";
|
||||
import {
|
||||
renderAboutSettingsTab,
|
||||
renderBetaTestSettingsTab,
|
||||
|
|
@ -45,7 +46,6 @@ import { renderMcpIntegrationSettingsTab } from "./components/features/settings/
|
|||
import { IframeModal } from "@/components/ui/modals/IframeModal";
|
||||
import { renderTaskTimerSettingTab } from "./components/features/settings/tabs/TaskTimerSettingsTab";
|
||||
import { renderBasesSettingsTab } from "./components/features/settings/tabs/BasesSettingsTab";
|
||||
import { WorkspaceData } from "./experimental/v2/types/workspace";
|
||||
import { renderWorkspaceSettingsTab } from "@/components/features/settings/tabs/WorkspaceSettingTab";
|
||||
|
||||
export class TaskProgressBarSettingTab extends PluginSettingTab {
|
||||
|
|
@ -321,6 +321,10 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
if (tab.id === "mcp-integration" && !Platform.isDesktopApp) {
|
||||
return;
|
||||
}
|
||||
// Skip workspaces tab from main navigation (accessed via dropdown)
|
||||
if (tab.id === "workspaces") {
|
||||
return;
|
||||
}
|
||||
const category = tab.category || "core";
|
||||
if (categories[category as keyof typeof categories]) {
|
||||
categories[category as keyof typeof categories].tabs.push(tab);
|
||||
|
|
@ -440,6 +444,18 @@ export class TaskProgressBarSettingTab extends PluginSettingTab {
|
|||
(settingsHeader as unknown as HTMLElement).style.display =
|
||||
"none";
|
||||
}
|
||||
|
||||
// Special handling for workspaces tab - ensure it's still accessible via dropdown
|
||||
if (tabId === "workspaces") {
|
||||
// Make sure the workspace section is visible even though the tab is hidden
|
||||
const workspaceSection = this.containerEl.querySelector(
|
||||
'[data-tab-id="workspaces"]'
|
||||
);
|
||||
if (workspaceSection) {
|
||||
(workspaceSection as unknown as HTMLElement).style.display = "block";
|
||||
workspaceSection.addClass("settings-tab-section-active");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public openTab(tabId: string) {
|
||||
|
|
|
|||
|
|
@ -1,11 +1,5 @@
|
|||
/* Settings Search Component Styles */
|
||||
|
||||
/* 搜索容器 */
|
||||
.tg-settings-search-container {
|
||||
margin-bottom: var(--size-4-4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
/* 搜索输入框容器 - 使用 Obsidian 标准输入框样式 */
|
||||
.tg-settings-search-input-container {
|
||||
position: relative;
|
||||
|
|
@ -209,10 +203,6 @@ input.tg-settings-search-input {
|
|||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.tg-settings-search-container {
|
||||
margin-bottom: var(--size-4-3);
|
||||
}
|
||||
|
||||
.tg-settings-search-results {
|
||||
max-height: 280px;
|
||||
border-radius: var(--radius-s);
|
||||
|
|
@ -258,13 +248,6 @@ input.tg-settings-search-input {
|
|||
background-clip: content-box;
|
||||
}
|
||||
|
||||
/* 确保搜索框在设置页面中的正确定位 */
|
||||
.task-genius-settings .tg-settings-search-container {
|
||||
order: -1; /* 确保搜索框显示在最前面 */
|
||||
margin-top: 0;
|
||||
margin-bottom: var(--size-4-4);
|
||||
}
|
||||
|
||||
/* 与设置标签页容器的间距调整 */
|
||||
.task-genius-settings .settings-tabs-categorized-container {
|
||||
margin-top: var(--size-4-3);
|
||||
|
|
|
|||
130
src/styles/workspace-settings-selector.css
Normal file
130
src/styles/workspace-settings-selector.css
Normal file
|
|
@ -0,0 +1,130 @@
|
|||
/* Workspace Settings Selector Styles */
|
||||
|
||||
/* Main container for the header bar */
|
||||
.tg-settings-header-bar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
margin-bottom: 20px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Container for workspace selector */
|
||||
.tg-workspace-selector-container {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Workspace selector button */
|
||||
.workspace-settings-selector {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.workspace-settings-selector-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 6px 12px;
|
||||
background-color: var(--background-modifier-form-field);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
min-width: 150px;
|
||||
height: 34px; /* Match search bar height */
|
||||
}
|
||||
|
||||
.workspace-settings-selector-button:hover {
|
||||
background-color: var(--background-modifier-hover);
|
||||
border-color: var(--background-modifier-border-hover);
|
||||
}
|
||||
|
||||
.workspace-settings-selector-button:active {
|
||||
background-color: var(--background-modifier-active-hover);
|
||||
}
|
||||
|
||||
/* Workspace icon */
|
||||
.workspace-settings-selector-button .workspace-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.workspace-settings-selector-button .workspace-icon svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Workspace name */
|
||||
.workspace-settings-selector-button .workspace-name {
|
||||
flex: 1;
|
||||
font-size: 14px;
|
||||
color: var(--text-normal);
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
/* Dropdown arrow icon */
|
||||
.workspace-settings-selector-button .workspace-dropdown-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.workspace-settings-selector-button .workspace-dropdown-icon svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
fill: var(--text-muted);
|
||||
transition: transform 0.15s ease;
|
||||
}
|
||||
|
||||
.workspace-settings-selector-button:hover .workspace-dropdown-icon svg {
|
||||
fill: var(--text-normal);
|
||||
}
|
||||
|
||||
/* Adjust search container to take remaining space */
|
||||
.tg-settings-header-bar .tg-settings-search-container {
|
||||
flex: 1;
|
||||
max-width: none;
|
||||
}
|
||||
|
||||
/* Ensure search input container fills width */
|
||||
.tg-settings-header-bar .tg-settings-search-input-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Main container adjustments */
|
||||
.tg-settings-main-container {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
/* Ensure search results appear below the header bar */
|
||||
.tg-settings-main-container .tg-settings-search-results {
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
/* Mobile responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.tg-settings-header-bar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.workspace-settings-selector-button {
|
||||
width: 100%;
|
||||
min-width: unset;
|
||||
}
|
||||
|
||||
.tg-settings-header-bar .tg-settings-search-container {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
115
styles.css
115
styles.css
File diff suppressed because one or more lines are too long
|
|
@ -153,5 +153,7 @@
|
|||
"9.9.0-beta.1": "0.15.2",
|
||||
"9.9.0-beta.2": "0.15.2",
|
||||
"9.9.0-beta.3": "0.15.2",
|
||||
"9.9.0-beta.4": "0.15.2"
|
||||
"9.9.0-beta.4": "0.15.2",
|
||||
"9.9.0-beta.5": "0.15.2",
|
||||
"9.9.0-beta.6": "0.15.2"
|
||||
}
|
||||
Loading…
Reference in a new issue