refactor(settings): convert File Task configuration to dynamic add/remove components

- Replace static text inputs with dynamic list components for metadata fields, tags, and paths
- Add individual add/edit/delete controls for each configuration item
- Rename "FileSource" to user-friendly "File Task" throughout UI
- Consolidate duplicate configuration sections in IndexSettingsTab
- Move dynamic component styles to setting-v2.css to prevent override
- Fix TypeScript error in ics-manager.ts (optional plugin parameter)
- Improve user experience with auto-focus on new inputs and instant save

The configuration now matches the pattern used for custom date formats,
providing a more intuitive and flexible interface for managing File Task
recognition strategies.
This commit is contained in:
Quorafind 2025-08-22 20:11:31 +08:00
parent a5884b332f
commit 96162afd9f
5 changed files with 653 additions and 331 deletions

View file

@ -1,5 +1,5 @@
/**
* FileSourceSettings - UI component for File as Task configuration
* FileTaskSettings - UI component for File Task configuration
*
* Provides a settings interface for configuring how files can be recognized
* and treated as tasks with various strategies and options.
@ -11,7 +11,7 @@ import type { FileSourceConfiguration } from "../../types/file-source";
import { t } from "../../translations/helper";
/**
* Create File as Task settings UI
* Create File Task settings UI
*/
export function createFileSourceSettings(
containerEl: HTMLElement,
@ -49,21 +49,12 @@ function createEnableToggle(
config: FileSourceConfiguration,
): void {
// Don't create duplicate header since we're now embedded in IndexSettingsTab
const desc = containerEl.createEl("p");
desc.innerHTML =
t(
"Enable files to be recognized as tasks based on their metadata properties. ",
) +
`<strong>${t("Note:")}</strong> ` +
t(
"This is an advanced feature that extends file metadata parsing with multiple recognition strategies.",
);
new Setting(containerEl)
.setName(t("Enable File as Task"))
.setName(t("Enable File Task"))
.setDesc(
t(
"Allow files to be recognized and treated as tasks based on their properties",
"Allow files to be recognized and treated as tasks based on their metadata, tags, or file paths. This provides advanced recognition strategies beyond simple metadata parsing.",
),
)
.addToggle((toggle) =>
@ -114,34 +105,109 @@ function createRecognitionStrategiesSection(
plugin.settings.fileSource.recognitionStrategies.metadata.enabled =
value;
await plugin.saveSettings();
// Refresh to show/hide fields
containerEl.empty();
createFileSourceSettings(containerEl, plugin);
}),
);
if (config.recognitionStrategies.metadata.enabled) {
new Setting(metadataContainer)
.setName(t("Task Fields"))
.setDesc(
t(
"Comma-separated list of metadata fields that indicate a task",
),
)
.addText((text) =>
text
.setPlaceholder("dueDate, status, priority, assigned")
.setValue(
config.recognitionStrategies.metadata.taskFields.join(
", ",
),
)
.onChange(async (value) => {
plugin.settings.fileSource.recognitionStrategies.metadata.taskFields =
value
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
await plugin.saveSettings();
}),
);
// Container for metadata fields list
const fieldsContainer = metadataContainer.createDiv({
cls: "task-genius-metadata-fields-container",
});
fieldsContainer.createEl("h4", {
text: t("Task Fields"),
cls: "task-genius-fields-header",
});
fieldsContainer.createEl("p", {
text: t(
"Add metadata fields that indicate a file should be treated as a task (e.g., dueDate, status, priority)",
),
cls: "setting-item-description",
});
// Container for field list
const fieldListContainer = fieldsContainer.createDiv({
cls: "task-genius-field-list",
});
// Function to render the field list
const renderFieldList = () => {
fieldListContainer.empty();
const fields = config.recognitionStrategies.metadata.taskFields ?? [];
// Render existing fields
fields.forEach((field, index) => {
const fieldItem = fieldListContainer.createDiv({
cls: "task-genius-field-item",
});
// Field input
const fieldInput = fieldItem.createEl("input", {
type: "text",
value: field,
cls: "task-genius-field-input",
placeholder: t("Enter metadata field name"),
});
fieldInput.addEventListener("input", async (e) => {
const target = e.target as HTMLInputElement;
plugin.settings.fileSource.recognitionStrategies.metadata.taskFields[index] =
target.value.trim();
await plugin.saveSettings();
});
// Delete button
const deleteBtn = fieldItem.createEl("button", {
cls: "task-genius-field-delete-btn",
text: "×",
attr: {
"aria-label": t("Delete field"),
title: t("Delete this field"),
},
});
deleteBtn.addEventListener("click", async () => {
plugin.settings.fileSource.recognitionStrategies.metadata.taskFields.splice(
index,
1,
);
await plugin.saveSettings();
renderFieldList();
});
});
// Add new field button
const addFieldBtn = fieldListContainer.createEl("button", {
cls: "task-genius-add-field-btn",
text: t("+ Add Metadata Field"),
});
addFieldBtn.addEventListener("click", async () => {
if (!plugin.settings.fileSource.recognitionStrategies.metadata.taskFields) {
plugin.settings.fileSource.recognitionStrategies.metadata.taskFields = [];
}
plugin.settings.fileSource.recognitionStrategies.metadata.taskFields.push("");
await plugin.saveSettings();
renderFieldList();
// Focus on the new input
const inputs = fieldListContainer.querySelectorAll(
".task-genius-field-input",
);
const lastInput = inputs[inputs.length - 1] as HTMLInputElement;
if (lastInput) {
lastInput.focus();
}
});
};
// Initial render
renderFieldList();
new Setting(metadataContainer)
.setName(t("Require All Fields"))
@ -178,28 +244,109 @@ function createRecognitionStrategiesSection(
plugin.settings.fileSource.recognitionStrategies.tags.enabled =
value;
await plugin.saveSettings();
// Refresh to show/hide fields
containerEl.empty();
createFileSourceSettings(containerEl, plugin);
}),
);
if (config.recognitionStrategies.tags.enabled) {
new Setting(tagContainer)
.setName(t("Task Tags"))
.setDesc(t("Comma-separated list of tags that indicate a task"))
.addText((text) =>
text
.setPlaceholder("#task, #actionable, #todo")
.setValue(
config.recognitionStrategies.tags.taskTags.join(", "),
)
.onChange(async (value) => {
plugin.settings.fileSource.recognitionStrategies.tags.taskTags =
value
.split(",")
.map((s) => s.trim())
.filter((s) => s.length > 0);
await plugin.saveSettings();
}),
);
// Container for tags list
const tagsContainer = tagContainer.createDiv({
cls: "task-genius-tags-container",
});
tagsContainer.createEl("h4", {
text: t("Task Tags"),
cls: "task-genius-tags-header",
});
tagsContainer.createEl("p", {
text: t(
"Add tags that indicate a file should be treated as a task (e.g., #task, #todo, #actionable)",
),
cls: "setting-item-description",
});
// Container for tag list
const tagListContainer = tagsContainer.createDiv({
cls: "task-genius-tag-list",
});
// Function to render the tag list
const renderTagList = () => {
tagListContainer.empty();
const tags = config.recognitionStrategies.tags.taskTags ?? [];
// Render existing tags
tags.forEach((tag, index) => {
const tagItem = tagListContainer.createDiv({
cls: "task-genius-tag-item",
});
// Tag input
const tagInput = tagItem.createEl("input", {
type: "text",
value: tag,
cls: "task-genius-tag-input",
placeholder: t("Enter tag (e.g., #task)"),
});
tagInput.addEventListener("input", async (e) => {
const target = e.target as HTMLInputElement;
plugin.settings.fileSource.recognitionStrategies.tags.taskTags[index] =
target.value.trim();
await plugin.saveSettings();
});
// Delete button
const deleteBtn = tagItem.createEl("button", {
cls: "task-genius-tag-delete-btn",
text: "×",
attr: {
"aria-label": t("Delete tag"),
title: t("Delete this tag"),
},
});
deleteBtn.addEventListener("click", async () => {
plugin.settings.fileSource.recognitionStrategies.tags.taskTags.splice(
index,
1,
);
await plugin.saveSettings();
renderTagList();
});
});
// Add new tag button
const addTagBtn = tagListContainer.createEl("button", {
cls: "task-genius-add-tag-btn",
text: t("+ Add Tag"),
});
addTagBtn.addEventListener("click", async () => {
if (!plugin.settings.fileSource.recognitionStrategies.tags.taskTags) {
plugin.settings.fileSource.recognitionStrategies.tags.taskTags = [];
}
plugin.settings.fileSource.recognitionStrategies.tags.taskTags.push("");
await plugin.saveSettings();
renderTagList();
// Focus on the new input
const inputs = tagListContainer.querySelectorAll(
".task-genius-tag-input",
);
const lastInput = inputs[inputs.length - 1] as HTMLInputElement;
if (lastInput) {
lastInput.focus();
}
});
};
// Initial render
renderTagList();
new Setting(tagContainer)
.setName(t("Tag Matching Mode"))
@ -232,7 +379,8 @@ function createRecognitionStrategiesSection(
toggle
.setValue(config.recognitionStrategies.paths.enabled)
.onChange(async (value) => {
plugin.settings.fileSource.recognitionStrategies.paths.enabled = value;
plugin.settings.fileSource.recognitionStrategies.paths.enabled =
value;
await plugin.saveSettings();
// Refresh settings interface
containerEl.empty();
@ -241,38 +389,121 @@ function createRecognitionStrategiesSection(
);
if (config.recognitionStrategies.paths.enabled) {
new Setting(pathContainer)
.setName(t("Task Paths"))
.setDesc(
t("Paths that contain task files. One per line. Examples: Projects/, Tasks/2024/, Work/TODO/")
)
.addTextArea((text) => {
text
.setPlaceholder("Projects/\nTasks/\nWork/TODO/")
.setValue(config.recognitionStrategies.paths.taskPaths.join("\n"))
.onChange(async (value) => {
plugin.settings.fileSource.recognitionStrategies.paths.taskPaths =
value
.split("\n")
.map((s) => s.trim())
.filter((s) => s.length > 0);
await plugin.saveSettings();
});
text.inputEl.rows = 4;
text.inputEl.cols = 40;
// Container for paths list
const pathsContainer = pathContainer.createDiv({
cls: "task-genius-paths-container",
});
pathsContainer.createEl("h4", {
text: t("Task Paths"),
cls: "task-genius-paths-header",
});
pathsContainer.createEl("p", {
text: t(
"Add paths that contain task files (e.g., Projects/, Tasks/2024/, Work/TODO/)",
),
cls: "setting-item-description",
});
// Container for path list
const pathListContainer = pathsContainer.createDiv({
cls: "task-genius-path-list",
});
// Function to render the path list
const renderPathList = () => {
pathListContainer.empty();
const paths = config.recognitionStrategies.paths.taskPaths ?? [];
// Render existing paths
paths.forEach((path, index) => {
const pathItem = pathListContainer.createDiv({
cls: "task-genius-path-item",
});
// Path input
const pathInput = pathItem.createEl("input", {
type: "text",
value: path,
cls: "task-genius-path-input",
placeholder: t("Enter path (e.g., Projects/, Tasks/**/*.md)"),
});
pathInput.addEventListener("input", async (e) => {
const target = e.target as HTMLInputElement;
plugin.settings.fileSource.recognitionStrategies.paths.taskPaths[index] =
target.value.trim();
await plugin.saveSettings();
});
// Delete button
const deleteBtn = pathItem.createEl("button", {
cls: "task-genius-path-delete-btn",
text: "×",
attr: {
"aria-label": t("Delete path"),
title: t("Delete this path"),
},
});
deleteBtn.addEventListener("click", async () => {
plugin.settings.fileSource.recognitionStrategies.paths.taskPaths.splice(
index,
1,
);
await plugin.saveSettings();
renderPathList();
});
});
// Add new path button
const addPathBtn = pathListContainer.createEl("button", {
cls: "task-genius-add-path-btn",
text: t("+ Add Path"),
});
addPathBtn.addEventListener("click", async () => {
if (!plugin.settings.fileSource.recognitionStrategies.paths.taskPaths) {
plugin.settings.fileSource.recognitionStrategies.paths.taskPaths = [];
}
plugin.settings.fileSource.recognitionStrategies.paths.taskPaths.push("");
await plugin.saveSettings();
renderPathList();
// Focus on the new input
const inputs = pathListContainer.querySelectorAll(
".task-genius-path-input",
);
const lastInput = inputs[inputs.length - 1] as HTMLInputElement;
if (lastInput) {
lastInput.focus();
}
});
};
// Initial render
renderPathList();
new Setting(pathContainer)
.setName(t("Path Matching Mode"))
.setDesc(t("How paths should be matched"))
.addDropdown((dropdown) =>
dropdown
.addOption("prefix", t("Prefix (e.g., Projects/ matches Projects/App.md)"))
.addOption("glob", t("Glob pattern (e.g., Projects/**/*.md)"))
.addOption(
"prefix",
t("Prefix (e.g., Projects/ matches Projects/App.md)"),
)
.addOption(
"glob",
t("Glob pattern (e.g., Projects/**/*.md)"),
)
.addOption("regex", t("Regular expression (advanced)"))
.setValue(config.recognitionStrategies.paths.matchMode)
.onChange(async (value: "prefix" | "regex" | "glob") => {
plugin.settings.fileSource.recognitionStrategies.paths.matchMode = value;
plugin.settings.fileSource.recognitionStrategies.paths.matchMode =
value;
await plugin.saveSettings();
// Refresh to show updated examples
containerEl.empty();
@ -283,43 +514,51 @@ function createRecognitionStrategiesSection(
// Add examples based on current mode
const examples = pathContainer.createDiv("setting-item-description");
examples.style.marginTop = "10px";
const currentMode = config.recognitionStrategies.paths.matchMode;
let exampleText = "";
switch (currentMode) {
case "prefix":
exampleText = t("Examples:") + "\n" +
"• Projects/ → " + t("matches all files under Projects folder") + "\n" +
"• Tasks/2024/ → " + t("matches all files under Tasks/2024 folder");
exampleText =
t("Examples:") +
"\n" +
"• Projects/ → " +
t("matches all files under Projects folder") +
"\n" +
"• Tasks/2024/ → " +
t("matches all files under Tasks/2024 folder");
break;
case "glob":
exampleText = t("Examples:") + "\n" +
"• Projects/**/*.md → " + t("all .md files in Projects and subfolders") + "\n" +
"• Tasks/*.task.md → " + t("files ending with .task.md in Tasks folder") + "\n" +
"• Work/*/TODO.md → " + t("TODO.md in any direct subfolder of Work");
exampleText =
t("Examples:") +
"\n" +
"• Projects/**/*.md → " +
t("all .md files in Projects and subfolders") +
"\n" +
"• Tasks/*.task.md → " +
t("files ending with .task.md in Tasks folder") +
"\n" +
"• Work/*/TODO.md → " +
t("TODO.md in any direct subfolder of Work");
break;
case "regex":
exampleText = t("Examples:") + "\n" +
"• ^Projects/.*\\.md$ → " + t("all .md files in Projects folder") + "\n" +
"• ^Tasks/\\d{4}-\\d{2}-\\d{2} → " + t("files starting with date in Tasks");
exampleText =
t("Examples:") +
"\n" +
"• ^Projects/.*\\.md$ → " +
t("all .md files in Projects folder") +
"\n" +
"• ^Tasks/\\d{4}-\\d{2}-\\d{2} → " +
t("files starting with date in Tasks");
break;
}
examples.createEl("pre", {
text: exampleText,
attr: { style: "font-size: 0.9em; color: var(--text-muted);" }
attr: { style: "font-size: 0.9em; color: var(--text-muted);" },
});
}
// Template strategy placeholder (keep for future)
const templateContainer = containerEl.createDiv(
"file-source-future-strategies",
);
templateContainer.createEl("p", {
text: t("Template-based recognition strategy will be available in a future update."),
attr: { style: "color: var(--text-muted); font-style: italic;" },
});
}
/**
@ -667,11 +906,11 @@ function createAdvancedSection(
// Statistics section
const statsContainer = containerEl.createDiv("file-source-stats");
statsContainer.createEl("h5", { text: t("File as Task Status") });
statsContainer.createEl("h5", { text: t("File Task Status") });
const statusText = config.enabled
? t("File as Task is enabled and monitoring files")
: t("File as Task is disabled");
? t("File Task is enabled and monitoring files")
: t("File Task is disabled");
statsContainer.createEl("p", { text: statusText });

View file

@ -3,6 +3,7 @@ import { TaskProgressBarSettingTab } from "../../setting";
import { t } from "../../translations/helper";
import { SingleFolderSuggest } from "../AutoComplete";
import { ConfirmModal } from "../ConfirmModal";
import { createFileSourceSettings } from "./FileSourceSettings";
/**
* Renders the Index Settings tab that consolidates all indexing-related settings
@ -409,232 +410,13 @@ export function renderIndexSettingsTab(
new Setting(containerEl)
.setName(t("File Task Configuration"))
.setDesc(
t("Configure how to extract tasks from file metadata and tags."),
t("Configure how files can be recognized and treated as tasks with various strategies."),
)
.setHeading();
new Setting(containerEl)
.setName(t("Enable file metadata parsing"))
.setDesc(
t(
"Parse tasks from file frontmatter metadata fields. When enabled, files with specific metadata fields will be treated as tasks.",
),
)
.addToggle((toggle) => {
toggle.setValue(
settingTab.plugin.settings.fileParsingConfig
.enableFileMetadataParsing,
);
toggle.onChange(async (value) => {
const previousValue =
settingTab.plugin.settings.fileParsingConfig
.enableFileMetadataParsing;
settingTab.plugin.settings.fileParsingConfig.enableFileMetadataParsing =
value;
settingTab.applySettingsUpdate();
// If file metadata parsing was just enabled, trigger a full reindex
if (!previousValue && value && settingTab.plugin.dataflowOrchestrator) {
try {
new Notice(
t(
"File metadata parsing enabled. Rebuilding task index...",
),
);
await settingTab.plugin.dataflowOrchestrator.rebuild();
new Notice(t("Task index rebuilt successfully"));
} catch (error) {
console.error(
"Failed to reindex after enabling file metadata parsing:",
error,
);
new Notice(t("Failed to rebuild task index"));
}
}
settingTab.display(); // Refresh to show/hide related settings
});
});
if (
settingTab.plugin.settings.fileParsingConfig.enableFileMetadataParsing
) {
new Setting(containerEl)
.setName(t("Metadata fields to parse as tasks"))
.setDesc(
t(
"Comma-separated list of metadata fields that should be treated as tasks (e.g., dueDate, todo, complete, task)",
),
)
.addText((text) => {
text.setPlaceholder("dueDate, todo, complete, task")
.setValue(
settingTab.plugin.settings.fileParsingConfig.metadataFieldsToParseAsTasks.join(
", ",
),
)
.onChange((value) => {
settingTab.plugin.settings.fileParsingConfig.metadataFieldsToParseAsTasks =
value
.split(",")
.map((field) => field.trim())
.filter((field) => field.length > 0);
settingTab.applySettingsUpdate();
});
});
new Setting(containerEl)
.setName(t("Task content from metadata"))
.setDesc(
t(
"Which metadata field to use as task content. If not found, will use filename.",
),
)
.addText((text) => {
text.setPlaceholder("title")
.setValue(
settingTab.plugin.settings.fileParsingConfig
.taskContentFromMetadata,
)
.onChange((value) => {
settingTab.plugin.settings.fileParsingConfig.taskContentFromMetadata =
value || "title";
settingTab.applySettingsUpdate();
});
});
new Setting(containerEl)
.setName(t("Default task status"))
.setDesc(
t(
"Default status for tasks created from metadata (space for incomplete, x for complete)",
),
)
.addText((text) => {
text.setPlaceholder(" ")
.setValue(
settingTab.plugin.settings.fileParsingConfig
.defaultTaskStatus,
)
.onChange((value) => {
settingTab.plugin.settings.fileParsingConfig.defaultTaskStatus =
value || " ";
settingTab.applySettingsUpdate();
});
});
}
new Setting(containerEl)
.setName(t("Enable tag-based task parsing"))
.setDesc(
t(
"Parse tasks from file tags. When enabled, files with specific tags will be treated as tasks.",
),
)
.addToggle((toggle) => {
toggle.setValue(
settingTab.plugin.settings.fileParsingConfig
.enableTagBasedTaskParsing,
);
toggle.onChange((value) => {
settingTab.plugin.settings.fileParsingConfig.enableTagBasedTaskParsing =
value;
settingTab.applySettingsUpdate();
settingTab.display(); // Refresh to show/hide related settings
});
});
if (
settingTab.plugin.settings.fileParsingConfig.enableTagBasedTaskParsing
) {
new Setting(containerEl)
.setName(t("Tags to parse as tasks"))
.setDesc(
t(
"Comma-separated list of tags that should be treated as tasks (e.g., #todo, #task, #action, #due)",
),
)
.addText((text) => {
text.setPlaceholder("#todo, #task, #action, #due")
.setValue(
settingTab.plugin.settings.fileParsingConfig.tagsToParseAsTasks.join(
", ",
),
)
.onChange((value) => {
settingTab.plugin.settings.fileParsingConfig.tagsToParseAsTasks =
value
.split(",")
.map((tag) => tag.trim())
.filter((tag) => tag.length > 0);
settingTab.applySettingsUpdate();
});
});
}
// FileSource Settings Integration
if (settingTab.plugin.settings.fileSource) {
new Setting(containerEl)
.setName(t("Enable FileSource feature"))
.setDesc(
t(
"Enable FileSource to recognize files as tasks based on metadata properties. Files can serve dual roles as both Projects and Tasks.",
),
)
.addToggle((toggle) => {
toggle
.setValue(settingTab.plugin.settings.fileSource.enabled)
.onChange(async (value) => {
settingTab.plugin.settings.fileSource.enabled = value;
settingTab.applySettingsUpdate();
settingTab.display();
});
});
if (settingTab.plugin.settings.fileSource.enabled) {
// Metadata recognition settings
new Setting(containerEl)
.setName(t("FileSource: Metadata-based recognition"))
.setDesc(
t(
"Recognize files as tasks when they contain specific metadata fields",
),
)
.addToggle((toggle) => {
toggle
.setValue(
settingTab.plugin.settings.fileSource
.recognitionStrategies.metadata.enabled,
)
.onChange(async (value) => {
settingTab.plugin.settings.fileSource.recognitionStrategies.metadata.enabled =
value;
settingTab.applySettingsUpdate();
});
});
// Tag recognition settings
new Setting(containerEl)
.setName(t("FileSource: Tag-based recognition"))
.setDesc(
t(
"Recognize files as tasks when they contain specific tags",
),
)
.addToggle((toggle) => {
toggle
.setValue(
settingTab.plugin.settings.fileSource
.recognitionStrategies.tags.enabled,
)
.onChange(async (value) => {
settingTab.plugin.settings.fileSource.recognitionStrategies.tags.enabled =
value;
settingTab.applySettingsUpdate();
});
});
}
}
// Create a dedicated container for File Task settings
const fileTaskContainer = containerEl.createDiv("file-task-settings-container");
createFileSourceSettings(fileTaskContainer, settingTab.plugin);
// ========================================
// SECTION 3: Performance Settings

View file

@ -31,12 +31,12 @@ export class IcsManager extends Component {
private onEventsUpdated?: (sourceId: string, events: IcsEvent[]) => void;
private pluginSettings: TaskProgressBarSettings;
private plugin: TaskProgressBarPlugin;
private plugin?: TaskProgressBarPlugin;
constructor(
config: IcsManagerConfig,
pluginSettings: TaskProgressBarSettings,
plugin: TaskProgressBarPlugin,
plugin?: TaskProgressBarPlugin,
) {
super();
this.config = config;

View file

@ -27,3 +27,163 @@
border-radius: 6px;
background-color: var(--background-secondary);
}
/* File Task dynamic configuration styles */
.task-genius-metadata-fields-container,
.task-genius-tags-container,
.task-genius-paths-container,
.task-genius-date-formats-container {
margin: 1rem 0;
padding: 1rem;
background: var(--background-secondary);
border-radius: 8px;
}
.task-genius-fields-header,
.task-genius-tags-header,
.task-genius-paths-header,
.task-genius-formats-header,
.task-genius-examples-header {
font-size: 0.95em;
font-weight: 600;
margin-bottom: 0.5rem;
color: var(--text-normal);
}
.task-genius-field-list,
.task-genius-tag-list,
.task-genius-path-list,
.task-genius-format-list {
display: flex;
flex-direction: column;
gap: 0.5rem;
margin-bottom: 1rem;
}
.task-genius-field-item,
.task-genius-tag-item,
.task-genius-path-item,
.task-genius-format-item {
display: flex;
align-items: center;
gap: 0.5rem;
background: var(--background-primary);
padding: 0.5rem;
border-radius: 4px;
border: 1px solid var(--background-modifier-border);
transition: border-color 0.2s;
}
.task-genius-field-item:hover,
.task-genius-tag-item:hover,
.task-genius-path-item:hover,
.task-genius-format-item:hover {
border-color: var(--interactive-accent);
}
.task-genius-field-input,
.task-genius-tag-input,
.task-genius-path-input,
.task-genius-format-input {
flex: 1;
padding: 0.4rem;
border: 1px solid var(--background-modifier-border);
border-radius: 4px;
background: var(--background-primary);
color: var(--text-normal);
font-family: var(--font-interface);
font-size: 0.9em;
}
.task-genius-field-input:focus,
.task-genius-tag-input:focus,
.task-genius-path-input:focus,
.task-genius-format-input:focus {
outline: none;
border-color: var(--interactive-accent);
box-shadow: 0 0 0 2px var(--interactive-accent-hover);
}
.task-genius-field-delete-btn,
.task-genius-tag-delete-btn,
.task-genius-path-delete-btn,
.task-genius-format-delete-btn {
width: 28px;
height: 28px;
padding: 0;
border: none;
background: var(--background-modifier-error);
color: var(--text-on-accent);
border-radius: 4px;
cursor: pointer;
font-size: 1.2em;
font-weight: bold;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}
.task-genius-field-delete-btn:hover,
.task-genius-tag-delete-btn:hover,
.task-genius-path-delete-btn:hover,
.task-genius-format-delete-btn:hover {
background: var(--background-modifier-error-hover);
}
.task-genius-add-field-btn,
.task-genius-add-tag-btn,
.task-genius-add-path-btn,
.task-genius-add-format-btn {
padding: 0.5rem 1rem;
background: var(--background-primary);
color: var(--text-muted);
border-radius: 4px;
cursor: pointer;
font-size: 0.9em;
transition: all 0.2s;
}
.task-genius-add-field-btn:hover,
.task-genius-add-tag-btn:hover,
.task-genius-add-path-btn:hover,
.task-genius-add-format-btn:hover {
border-color: var(--interactive-accent);
color: var(--interactive-accent);
}
/* Date format examples table */
.task-genius-date-examples {
margin-top: 1rem;
padding: 1rem;
background: var(--background-secondary);
border-radius: 8px;
}
.task-genius-date-examples-table {
width: 100%;
border-collapse: collapse;
font-size: 0.9em;
}
.task-genius-date-examples-table th {
text-align: left;
padding: 0.5rem;
border-bottom: 1px solid var(--background-modifier-border);
color: var(--text-muted);
font-weight: 500;
}
.task-genius-date-examples-table td {
padding: 0.4rem 0.5rem;
border-bottom: 1px solid var(--background-modifier-border-hover);
color: var(--text-normal);
}
.task-genius-date-examples-table tr:last-child td {
border-bottom: none;
}
.task-genius-date-examples-table tr:hover td {
background: var(--background-primary-alt);
}

File diff suppressed because one or more lines are too long