mirror of
https://github.com/callumalpass/tasknotes.git
synced 2026-07-22 12:50:26 +00:00
refactor: consolidate property settings into unified Task Properties tab
- Move property key, default value, and NLP trigger settings into unified property cards in the Task Properties tab - Remove Defaults tab entirely (body template moved to Features tab) - Move title/filename settings from Appearance tab into Title property card - Move project autosuggest settings from Appearance tab into Projects card - Add collapsible sections for status/priority values, autosuggest filters, and display customization - Add onCollapseChange callback to CardComponent for preserving collapse state - Add "Feature Properties" section for pomodoro and calendar sync properties - Improve styling for default project chips and remove button shadows - Add right-aligned inputs in card components
This commit is contained in:
parent
3d6192da59
commit
d9c4b2f196
8 changed files with 1993 additions and 1526 deletions
|
|
@ -823,6 +823,145 @@ export const en: TranslationTree = {
|
|||
},
|
||||
},
|
||||
taskProperties: {
|
||||
// Section headers for property card layout
|
||||
sections: {
|
||||
coreProperties: "Core Properties",
|
||||
corePropertiesDesc: "Status and priority are the core properties that define a task's state and importance.",
|
||||
dateProperties: "Date Properties",
|
||||
datePropertiesDesc: "Configure when tasks are due and scheduled.",
|
||||
organizationProperties: "Organization Properties",
|
||||
organizationPropertiesDesc: "Organize tasks with contexts, projects, and tags.",
|
||||
taskDetails: "Task Details",
|
||||
taskDetailsDesc: "Additional details like time estimates, recurrence, and reminders.",
|
||||
metadataProperties: "Metadata Properties",
|
||||
metadataPropertiesDesc: "System-managed properties for tracking task history.",
|
||||
featureProperties: "Feature Properties",
|
||||
featurePropertiesDesc: "Properties used by specific TaskNotes features like Pomodoro timer and calendar sync.",
|
||||
},
|
||||
// Property card common fields
|
||||
propertyCard: {
|
||||
propertyKey: "Property key:",
|
||||
default: "Default:",
|
||||
nlpTrigger: "NLP trigger:",
|
||||
triggerChar: "Trigger character:",
|
||||
triggerEmpty: "Trigger cannot be empty",
|
||||
triggerTooLong: "Trigger is too long (max 10 characters)",
|
||||
},
|
||||
// Individual property names and descriptions
|
||||
properties: {
|
||||
status: {
|
||||
name: "Status",
|
||||
description: "Task status indicating completion state",
|
||||
},
|
||||
priority: {
|
||||
name: "Priority",
|
||||
description: "Task priority level",
|
||||
},
|
||||
due: {
|
||||
name: "Due Date",
|
||||
description: "When the task must be completed",
|
||||
},
|
||||
scheduled: {
|
||||
name: "Scheduled Date",
|
||||
description: "When to work on the task",
|
||||
},
|
||||
contexts: {
|
||||
name: "Contexts",
|
||||
description: "Where or how the task can be done (e.g., @home, @work)",
|
||||
},
|
||||
projects: {
|
||||
name: "Projects",
|
||||
description: "Projects this task belongs to",
|
||||
},
|
||||
tags: {
|
||||
name: "Tags",
|
||||
description: "Obsidian tags for categorization",
|
||||
},
|
||||
timeEstimate: {
|
||||
name: "Time Estimate",
|
||||
description: "Estimated time to complete the task",
|
||||
},
|
||||
recurrence: {
|
||||
name: "Recurrence",
|
||||
description: "Pattern for repeating tasks",
|
||||
},
|
||||
reminders: {
|
||||
name: "Reminders",
|
||||
description: "Notifications before task deadlines",
|
||||
},
|
||||
title: {
|
||||
name: "Title",
|
||||
description: "The task title/name",
|
||||
},
|
||||
dateCreated: {
|
||||
name: "Date Created",
|
||||
description: "When the task was created",
|
||||
},
|
||||
dateModified: {
|
||||
name: "Date Modified",
|
||||
description: "When the task was last modified",
|
||||
},
|
||||
completedDate: {
|
||||
name: "Completed Date",
|
||||
description: "When the task was completed",
|
||||
},
|
||||
archiveTag: {
|
||||
name: "Archive Tag",
|
||||
description: "Tag used to mark archived tasks",
|
||||
},
|
||||
timeEntries: {
|
||||
name: "Time Entries",
|
||||
description: "Time tracking entries for the task",
|
||||
},
|
||||
completeInstances: {
|
||||
name: "Complete Instances",
|
||||
description: "Completion history for recurring tasks",
|
||||
},
|
||||
blockedBy: {
|
||||
name: "Blocked By",
|
||||
description: "Tasks that must be completed first",
|
||||
},
|
||||
pomodoros: {
|
||||
name: "Pomodoros",
|
||||
description: "Pomodoro session count for the task",
|
||||
},
|
||||
icsEventId: {
|
||||
name: "ICS Event ID",
|
||||
description: "Calendar event identifier",
|
||||
},
|
||||
icsEventTag: {
|
||||
name: "ICS Event Tag",
|
||||
description: "Tag for calendar event linking",
|
||||
},
|
||||
},
|
||||
// Card-specific labels
|
||||
statusCard: {
|
||||
valuesHeader: "Status Values",
|
||||
},
|
||||
priorityCard: {
|
||||
valuesHeader: "Priority Values",
|
||||
},
|
||||
projectsCard: {
|
||||
defaultProjects: "Default projects:",
|
||||
useParentNote: "Use parent note as project:",
|
||||
noDefaultProjects: "No default projects selected",
|
||||
autosuggestFilters: "Autosuggest Filters",
|
||||
customizeDisplay: "Customize Display",
|
||||
filtersOn: "Filters On",
|
||||
},
|
||||
titleCard: {
|
||||
storeTitleInFilename: "Store title in filename:",
|
||||
storedInFilename: "Stored in filename",
|
||||
filenameUpdatesWithTitle: "Filename will automatically update when the task title changes.",
|
||||
filenameFormat: "Filename format:",
|
||||
customTemplate: "Custom template:",
|
||||
},
|
||||
tagsCard: {
|
||||
nativeObsidianTags: "Uses native Obsidian tags",
|
||||
},
|
||||
remindersCard: {
|
||||
defaultReminders: "Default Reminders",
|
||||
},
|
||||
taskStatuses: {
|
||||
header: "Task Statuses",
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import { debounce } from "./components/settingHelpers";
|
|||
import { renderGeneralTab } from "./tabs/generalTab";
|
||||
import { renderTaskPropertiesTab } from "./tabs/taskPropertiesTab";
|
||||
import { renderModalFieldsTab } from "./tabs/modalFieldsTab";
|
||||
import { renderDefaultsTab } from "./tabs/defaultsTab";
|
||||
import { renderAppearanceTab } from "./tabs/appearanceTab";
|
||||
import { renderFeaturesTab } from "./tabs/featuresTab";
|
||||
import { renderIntegrationsTab } from "./tabs/integrationsTab";
|
||||
|
|
@ -47,7 +46,7 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
// Create tab navigation
|
||||
const tabNav = containerEl.createDiv("settings-tab-nav settings-view__tab-nav");
|
||||
|
||||
// Define the new 7-tab structure
|
||||
// Define the 6-tab structure (defaults merged into task-properties)
|
||||
const allTabs: TabConfig[] = [
|
||||
{
|
||||
id: "general",
|
||||
|
|
@ -64,11 +63,6 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
nameKey: "settings.tabs.modalFields",
|
||||
renderFn: renderModalFieldsTab,
|
||||
},
|
||||
{
|
||||
id: "defaults",
|
||||
nameKey: "settings.tabs.defaults",
|
||||
renderFn: renderDefaultsTab,
|
||||
},
|
||||
{
|
||||
id: "appearance",
|
||||
nameKey: "settings.tabs.appearance",
|
||||
|
|
@ -221,11 +215,6 @@ export class TaskNotesSettingTab extends PluginSettingTab {
|
|||
nameKey: "settings.tabs.modalFields",
|
||||
renderFn: renderModalFieldsTab,
|
||||
},
|
||||
{
|
||||
id: "defaults",
|
||||
nameKey: "settings.tabs.defaults",
|
||||
renderFn: renderDefaultsTab,
|
||||
},
|
||||
{
|
||||
id: "appearance",
|
||||
nameKey: "settings.tabs.appearance",
|
||||
|
|
|
|||
|
|
@ -189,6 +189,8 @@ export interface CardConfig {
|
|||
collapsible?: boolean;
|
||||
/** Whether the card should start collapsed (only applies if collapsible is true) */
|
||||
defaultCollapsed?: boolean;
|
||||
/** Callback when collapse state changes */
|
||||
onCollapseChange?: (collapsed: boolean) => void;
|
||||
/** Color indicator on the left side of the card */
|
||||
colorIndicator?: {
|
||||
color: string;
|
||||
|
|
@ -364,10 +366,12 @@ export function createCard(container: HTMLElement, config: CardConfig): HTMLElem
|
|||
// Expand
|
||||
card.removeClass("tasknotes-settings__card--collapsed");
|
||||
header.title = "Collapse card";
|
||||
config.onCollapseChange?.(false);
|
||||
} else {
|
||||
// Collapse
|
||||
card.addClass("tasknotes-settings__card--collapsed");
|
||||
header.title = "Expand card";
|
||||
config.onCollapseChange?.(true);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -59,84 +59,7 @@ export function renderAppearanceTab(
|
|||
const currentLabels = getPropertyLabels(plugin, currentProperties);
|
||||
createHelpText(container, `Currently showing: ${currentLabels.join(", ")}`);
|
||||
|
||||
|
||||
// Task Filenames Section
|
||||
createSectionHeader(container, translate("settings.appearance.taskFilenames.header"));
|
||||
createHelpText(container, translate("settings.appearance.taskFilenames.description"));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.appearance.taskFilenames.storeTitleInFilename.name"),
|
||||
desc: translate("settings.appearance.taskFilenames.storeTitleInFilename.description"),
|
||||
getValue: () => plugin.settings.storeTitleInFilename,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.storeTitleInFilename = value;
|
||||
save();
|
||||
// Re-render to show/hide other options
|
||||
renderAppearanceTab(container, plugin, save);
|
||||
},
|
||||
});
|
||||
|
||||
if (!plugin.settings.storeTitleInFilename) {
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.appearance.taskFilenames.filenameFormat.name"),
|
||||
desc: translate("settings.appearance.taskFilenames.filenameFormat.description"),
|
||||
options: [
|
||||
{
|
||||
value: "title",
|
||||
label: translate(
|
||||
"settings.appearance.taskFilenames.filenameFormat.options.title"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "zettel",
|
||||
label: translate(
|
||||
"settings.appearance.taskFilenames.filenameFormat.options.zettel"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "timestamp",
|
||||
label: translate(
|
||||
"settings.appearance.taskFilenames.filenameFormat.options.timestamp"
|
||||
),
|
||||
},
|
||||
{
|
||||
value: "custom",
|
||||
label: translate(
|
||||
"settings.appearance.taskFilenames.filenameFormat.options.custom"
|
||||
),
|
||||
},
|
||||
],
|
||||
getValue: () => plugin.settings.taskFilenameFormat,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskFilenameFormat = value as any;
|
||||
save();
|
||||
// Re-render to update visibility
|
||||
renderAppearanceTab(container, plugin, save);
|
||||
},
|
||||
ariaLabel: "Task filename generation format",
|
||||
});
|
||||
|
||||
if (plugin.settings.taskFilenameFormat === "custom") {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.taskFilenames.customTemplate.name"),
|
||||
desc: translate("settings.appearance.taskFilenames.customTemplate.description"),
|
||||
placeholder: translate(
|
||||
"settings.appearance.taskFilenames.customTemplate.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.customFilenameTemplate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.customFilenameTemplate = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Custom filename template with variables",
|
||||
});
|
||||
|
||||
createHelpText(
|
||||
container,
|
||||
translate("settings.appearance.taskFilenames.customTemplate.helpText")
|
||||
);
|
||||
}
|
||||
}
|
||||
// Note: Task Filenames settings have been moved to the Title property card in Task Properties tab
|
||||
|
||||
// Display Formatting Section
|
||||
createSectionHeader(container, translate("settings.appearance.displayFormatting.header"));
|
||||
|
|
@ -710,250 +633,4 @@ export function renderAppearanceTab(
|
|||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// Project Autosuggest Section
|
||||
createSectionHeader(container, translate("settings.appearance.projectAutosuggest.header"));
|
||||
createHelpText(container, translate("settings.appearance.projectAutosuggest.description"));
|
||||
|
||||
// Tag filtering
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.requiredTags.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.requiredTags.description"),
|
||||
placeholder: translate("settings.appearance.projectAutosuggest.requiredTags.placeholder"),
|
||||
getValue: () => plugin.settings.projectAutosuggest?.requiredTags?.join(", ") ?? "",
|
||||
setValue: async (value: string) => {
|
||||
if (!plugin.settings.projectAutosuggest) {
|
||||
plugin.settings.projectAutosuggest = {
|
||||
enableFuzzy: false,
|
||||
rows: [],
|
||||
showAdvanced: false,
|
||||
requiredTags: [],
|
||||
includeFolders: [],
|
||||
propertyKey: "",
|
||||
propertyValue: "",
|
||||
};
|
||||
}
|
||||
plugin.settings.projectAutosuggest.requiredTags = value
|
||||
.split(",")
|
||||
.map((tag) => tag.trim())
|
||||
.filter((tag) => tag.length > 0);
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Required tags for project suggestions",
|
||||
});
|
||||
|
||||
// Folder filtering
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.includeFolders.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.includeFolders.description"),
|
||||
placeholder: translate("settings.appearance.projectAutosuggest.includeFolders.placeholder"),
|
||||
getValue: () => plugin.settings.projectAutosuggest?.includeFolders?.join(", ") ?? "",
|
||||
setValue: async (value: string) => {
|
||||
if (!plugin.settings.projectAutosuggest) {
|
||||
plugin.settings.projectAutosuggest = {
|
||||
enableFuzzy: false,
|
||||
rows: [],
|
||||
showAdvanced: false,
|
||||
requiredTags: [],
|
||||
includeFolders: [],
|
||||
propertyKey: "",
|
||||
propertyValue: "",
|
||||
};
|
||||
}
|
||||
plugin.settings.projectAutosuggest.includeFolders = value
|
||||
.split(",")
|
||||
.map((folder) => folder.trim())
|
||||
.filter((folder) => folder.length > 0);
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Include folders for project suggestions",
|
||||
});
|
||||
|
||||
// Property filtering
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.requiredPropertyKey.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.requiredPropertyKey.description"),
|
||||
placeholder: translate(
|
||||
"settings.appearance.projectAutosuggest.requiredPropertyKey.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.projectAutosuggest?.propertyKey ?? "",
|
||||
setValue: async (value: string) => {
|
||||
if (!plugin.settings.projectAutosuggest) {
|
||||
plugin.settings.projectAutosuggest = {
|
||||
enableFuzzy: false,
|
||||
rows: [],
|
||||
showAdvanced: false,
|
||||
requiredTags: [],
|
||||
includeFolders: [],
|
||||
propertyKey: "",
|
||||
propertyValue: "",
|
||||
};
|
||||
}
|
||||
plugin.settings.projectAutosuggest.propertyKey = value.trim();
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Required frontmatter property key for project suggestions",
|
||||
debounceMs: 500, // Prevent rapid save calls while typing
|
||||
});
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.requiredPropertyValue.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.requiredPropertyValue.description"),
|
||||
placeholder: translate(
|
||||
"settings.appearance.projectAutosuggest.requiredPropertyValue.placeholder"
|
||||
),
|
||||
getValue: () => plugin.settings.projectAutosuggest?.propertyValue ?? "",
|
||||
setValue: async (value: string) => {
|
||||
if (!plugin.settings.projectAutosuggest) {
|
||||
plugin.settings.projectAutosuggest = {
|
||||
enableFuzzy: false,
|
||||
rows: [],
|
||||
showAdvanced: false,
|
||||
requiredTags: [],
|
||||
includeFolders: [],
|
||||
propertyKey: "",
|
||||
propertyValue: "",
|
||||
};
|
||||
}
|
||||
plugin.settings.projectAutosuggest.propertyValue = value.trim();
|
||||
save();
|
||||
},
|
||||
ariaLabel: "Required frontmatter property value for project suggestions",
|
||||
debounceMs: 500, // Prevent rapid save calls while typing
|
||||
});
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.customizeDisplay.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.customizeDisplay.description"),
|
||||
getValue: () => plugin.settings.projectAutosuggest?.showAdvanced ?? false,
|
||||
setValue: async (value: boolean) => {
|
||||
if (!plugin.settings.projectAutosuggest) {
|
||||
plugin.settings.projectAutosuggest = {
|
||||
enableFuzzy: false,
|
||||
rows: [],
|
||||
showAdvanced: false,
|
||||
requiredTags: [],
|
||||
includeFolders: [],
|
||||
propertyKey: "",
|
||||
propertyValue: "",
|
||||
};
|
||||
}
|
||||
plugin.settings.projectAutosuggest.showAdvanced = value;
|
||||
save();
|
||||
// Refresh the settings display
|
||||
renderAppearanceTab(container, plugin, save);
|
||||
},
|
||||
});
|
||||
|
||||
// Only show advanced settings if enabled
|
||||
if (plugin.settings.projectAutosuggest?.showAdvanced) {
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.enableFuzzyMatching.name"),
|
||||
desc: translate(
|
||||
"settings.appearance.projectAutosuggest.enableFuzzyMatching.description"
|
||||
),
|
||||
getValue: () => plugin.settings.projectAutosuggest?.enableFuzzy ?? false,
|
||||
setValue: async (value: boolean) => {
|
||||
if (!plugin.settings.projectAutosuggest) {
|
||||
plugin.settings.projectAutosuggest = {
|
||||
enableFuzzy: false,
|
||||
rows: [],
|
||||
showAdvanced: false,
|
||||
requiredTags: [],
|
||||
includeFolders: [],
|
||||
propertyKey: "",
|
||||
propertyValue: "",
|
||||
};
|
||||
}
|
||||
plugin.settings.projectAutosuggest.enableFuzzy = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// Display rows configuration
|
||||
createHelpText(
|
||||
container,
|
||||
translate("settings.appearance.projectAutosuggest.displayRowsHelp")
|
||||
);
|
||||
|
||||
const getRows = (): string[] =>
|
||||
(plugin.settings.projectAutosuggest?.rows ?? []).slice(0, 3);
|
||||
|
||||
const setRow = async (idx: number, value: string) => {
|
||||
if (!plugin.settings.projectAutosuggest) {
|
||||
plugin.settings.projectAutosuggest = {
|
||||
enableFuzzy: false,
|
||||
rows: [],
|
||||
showAdvanced: false,
|
||||
requiredTags: [],
|
||||
includeFolders: [],
|
||||
propertyKey: "",
|
||||
propertyValue: "",
|
||||
};
|
||||
}
|
||||
const current = plugin.settings.projectAutosuggest.rows ?? [];
|
||||
const next = [...current];
|
||||
next[idx] = value;
|
||||
plugin.settings.projectAutosuggest.rows = next.slice(0, 3);
|
||||
save();
|
||||
};
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.displayRows.row1.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.displayRows.row1.description"),
|
||||
placeholder: translate(
|
||||
"settings.appearance.projectAutosuggest.displayRows.row1.placeholder"
|
||||
),
|
||||
getValue: () => getRows()[0] || "",
|
||||
setValue: async (value: string) => setRow(0, value),
|
||||
ariaLabel: "Project autosuggest display row 1",
|
||||
});
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.displayRows.row2.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.displayRows.row2.description"),
|
||||
placeholder: translate(
|
||||
"settings.appearance.projectAutosuggest.displayRows.row2.placeholder"
|
||||
),
|
||||
getValue: () => getRows()[1] || "",
|
||||
setValue: async (value: string) => setRow(1, value),
|
||||
ariaLabel: "Project autosuggest display row 2",
|
||||
});
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.appearance.projectAutosuggest.displayRows.row3.name"),
|
||||
desc: translate("settings.appearance.projectAutosuggest.displayRows.row3.description"),
|
||||
placeholder: translate(
|
||||
"settings.appearance.projectAutosuggest.displayRows.row3.placeholder"
|
||||
),
|
||||
getValue: () => getRows()[2] || "",
|
||||
setValue: async (value: string) => setRow(2, value),
|
||||
ariaLabel: "Project autosuggest display row 3",
|
||||
});
|
||||
|
||||
// Concise help section
|
||||
const helpContainer = container.createDiv("tasknotes-settings__help-section");
|
||||
helpContainer.createEl("h4", {
|
||||
text: translate("settings.appearance.projectAutosuggest.quickReference.header"),
|
||||
});
|
||||
const helpList = helpContainer.createEl("ul");
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.appearance.projectAutosuggest.quickReference.properties"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.appearance.projectAutosuggest.quickReference.labels"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.appearance.projectAutosuggest.quickReference.searchable"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.appearance.projectAutosuggest.quickReference.staticText"),
|
||||
});
|
||||
helpContainer.createEl("p", {
|
||||
text: translate(
|
||||
"settings.appearance.projectAutosuggest.quickReference.alwaysSearchable"
|
||||
),
|
||||
cls: "settings-help-note",
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,674 +0,0 @@
|
|||
import { TAbstractFile, Setting } from "obsidian";
|
||||
import TaskNotesPlugin from "../../main";
|
||||
import { DefaultReminder } from "../../types/settings";
|
||||
import {
|
||||
createSectionHeader,
|
||||
createTextSetting,
|
||||
createToggleSetting,
|
||||
createDropdownSetting,
|
||||
createNumberSetting,
|
||||
createHelpText,
|
||||
} from "../components/settingHelpers";
|
||||
import {
|
||||
createCard,
|
||||
createDeleteHeaderButton,
|
||||
createCardInput,
|
||||
createCardSelect,
|
||||
createCardNumberInput,
|
||||
showCardEmptyState,
|
||||
CardRow,
|
||||
} from "../components/CardComponent";
|
||||
import { ProjectSelectModal } from "../../modals/ProjectSelectModal";
|
||||
import { splitListPreservingLinksAndQuotes } from "../../utils/stringSplit";
|
||||
import type { TranslationKey } from "../../i18n";
|
||||
|
||||
// interface ReminderItem extends ListEditorItem, DefaultReminder {}
|
||||
|
||||
/**
|
||||
* Renders the Defaults & Templates tab - settings for speeding up new task creation
|
||||
*/
|
||||
export function renderDefaultsTab(
|
||||
container: HTMLElement,
|
||||
plugin: TaskNotesPlugin,
|
||||
save: () => void
|
||||
): void {
|
||||
container.empty();
|
||||
|
||||
const translate = (key: TranslationKey, params?: Record<string, string | number>) =>
|
||||
plugin.i18n.translate(key, params);
|
||||
|
||||
// Basic Defaults Section
|
||||
createSectionHeader(container, translate("settings.defaults.header.basicDefaults"));
|
||||
createHelpText(container, translate("settings.defaults.description.basicDefaults"));
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.defaults.basicDefaults.defaultStatus.name"),
|
||||
desc: translate("settings.defaults.basicDefaults.defaultStatus.description"),
|
||||
options: plugin.settings.customStatuses.map((status) => ({
|
||||
value: status.value,
|
||||
label: status.label || status.value,
|
||||
})),
|
||||
getValue: () => plugin.settings.defaultTaskStatus,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.defaultTaskStatus = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.defaults.basicDefaults.defaultPriority.name"),
|
||||
desc: translate("settings.defaults.basicDefaults.defaultPriority.description"),
|
||||
options: [
|
||||
{ value: "", label: translate("settings.defaults.options.noDefault") },
|
||||
...plugin.settings.customPriorities.map((priority) => ({
|
||||
value: priority.value,
|
||||
label: priority.label || priority.value,
|
||||
})),
|
||||
],
|
||||
getValue: () => plugin.settings.defaultTaskPriority,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.defaultTaskPriority = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.defaults.basicDefaults.defaultContexts.name"),
|
||||
desc: translate("settings.defaults.basicDefaults.defaultContexts.description"),
|
||||
placeholder: translate("settings.defaults.basicDefaults.defaultContexts.placeholder"),
|
||||
getValue: () => plugin.settings.taskCreationDefaults.defaultContexts,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskCreationDefaults.defaultContexts = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.defaults.basicDefaults.defaultTags.name"),
|
||||
desc: translate("settings.defaults.basicDefaults.defaultTags.description"),
|
||||
placeholder: translate("settings.defaults.basicDefaults.defaultTags.placeholder"),
|
||||
getValue: () => plugin.settings.taskCreationDefaults.defaultTags,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskCreationDefaults.defaultTags = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// Default projects with file picker
|
||||
const selectedDefaultProjectFiles: TAbstractFile[] = [];
|
||||
const defaultProjectsContainer = container.createDiv("default-projects-container");
|
||||
const defaultProjectsSettingDiv = defaultProjectsContainer.createDiv();
|
||||
|
||||
new Setting(defaultProjectsSettingDiv)
|
||||
.setName(translate("settings.defaults.basicDefaults.defaultProjects.name"))
|
||||
.setDesc(translate("settings.defaults.basicDefaults.defaultProjects.description"))
|
||||
.addButton((button) => {
|
||||
button
|
||||
.setButtonText(
|
||||
translate("settings.defaults.basicDefaults.defaultProjects.selectButton")
|
||||
)
|
||||
.setTooltip(
|
||||
translate("settings.defaults.basicDefaults.defaultProjects.selectTooltip")
|
||||
)
|
||||
.onClick(() => {
|
||||
const modal = new ProjectSelectModal(
|
||||
plugin.app,
|
||||
plugin,
|
||||
(file: TAbstractFile) => {
|
||||
// Add the selected file if not already in the list
|
||||
if (!selectedDefaultProjectFiles.includes(file)) {
|
||||
selectedDefaultProjectFiles.push(file);
|
||||
const projectLinks = selectedDefaultProjectFiles
|
||||
.map((f) => `[[${f.path.replace(/\.md$/, "")}]]`)
|
||||
.join(", ");
|
||||
plugin.settings.taskCreationDefaults.defaultProjects = projectLinks;
|
||||
save();
|
||||
renderDefaultProjectsList(
|
||||
defaultProjectsContainer,
|
||||
plugin,
|
||||
save,
|
||||
selectedDefaultProjectFiles,
|
||||
translate
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
modal.open();
|
||||
});
|
||||
button.buttonEl.addClass("tn-btn");
|
||||
button.buttonEl.addClass("tn-btn--ghost");
|
||||
});
|
||||
|
||||
// Initialize selected projects from settings
|
||||
if (plugin.settings.taskCreationDefaults.defaultProjects) {
|
||||
const projectPaths = splitListPreservingLinksAndQuotes(
|
||||
plugin.settings.taskCreationDefaults.defaultProjects
|
||||
)
|
||||
.map((link) => link.replace(/\[\[|\]\]/g, "").trim())
|
||||
.filter((path) => path);
|
||||
|
||||
projectPaths.forEach((path) => {
|
||||
const file =
|
||||
plugin.app.vault.getAbstractFileByPath(path + ".md") ||
|
||||
plugin.app.vault.getAbstractFileByPath(path);
|
||||
if (file) {
|
||||
selectedDefaultProjectFiles.push(file);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
renderDefaultProjectsList(
|
||||
defaultProjectsContainer,
|
||||
plugin,
|
||||
save,
|
||||
selectedDefaultProjectFiles,
|
||||
translate
|
||||
);
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.defaults.basicDefaults.useParentNoteAsProject.name"),
|
||||
desc: translate("settings.defaults.basicDefaults.useParentNoteAsProject.description"),
|
||||
getValue: () => plugin.settings.taskCreationDefaults.useParentNoteAsProject,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.taskCreationDefaults.useParentNoteAsProject = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createNumberSetting(container, {
|
||||
name: translate("settings.defaults.basicDefaults.defaultTimeEstimate.name"),
|
||||
desc: translate("settings.defaults.basicDefaults.defaultTimeEstimate.description"),
|
||||
placeholder: translate("settings.defaults.basicDefaults.defaultTimeEstimate.placeholder"),
|
||||
min: 0,
|
||||
getValue: () => plugin.settings.taskCreationDefaults.defaultTimeEstimate,
|
||||
setValue: async (value: number) => {
|
||||
plugin.settings.taskCreationDefaults.defaultTimeEstimate = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.defaults.basicDefaults.defaultRecurrence.name"),
|
||||
desc: translate("settings.defaults.basicDefaults.defaultRecurrence.description"),
|
||||
options: [
|
||||
{ value: "none", label: translate("settings.defaults.options.none") },
|
||||
{ value: "daily", label: translate("settings.defaults.options.daily") },
|
||||
{ value: "weekly", label: translate("settings.defaults.options.weekly") },
|
||||
{ value: "monthly", label: translate("settings.defaults.options.monthly") },
|
||||
{ value: "yearly", label: translate("settings.defaults.options.yearly") },
|
||||
],
|
||||
getValue: () => plugin.settings.taskCreationDefaults.defaultRecurrence,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskCreationDefaults.defaultRecurrence = value as any;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// Date Defaults Section
|
||||
createSectionHeader(container, translate("settings.defaults.header.dateDefaults"));
|
||||
createHelpText(container, translate("settings.defaults.description.dateDefaults"));
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.defaults.dateDefaults.defaultDueDate.name"),
|
||||
desc: translate("settings.defaults.dateDefaults.defaultDueDate.description"),
|
||||
options: [
|
||||
{ value: "none", label: translate("settings.defaults.options.none") },
|
||||
{ value: "today", label: translate("settings.defaults.options.today") },
|
||||
{ value: "tomorrow", label: translate("settings.defaults.options.tomorrow") },
|
||||
{ value: "next-week", label: translate("settings.defaults.options.nextWeek") },
|
||||
],
|
||||
getValue: () => plugin.settings.taskCreationDefaults.defaultDueDate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskCreationDefaults.defaultDueDate = value as any;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
createDropdownSetting(container, {
|
||||
name: translate("settings.defaults.dateDefaults.defaultScheduledDate.name"),
|
||||
desc: translate("settings.defaults.dateDefaults.defaultScheduledDate.description"),
|
||||
options: [
|
||||
{ value: "none", label: translate("settings.defaults.options.none") },
|
||||
{ value: "today", label: translate("settings.defaults.options.today") },
|
||||
{ value: "tomorrow", label: translate("settings.defaults.options.tomorrow") },
|
||||
{ value: "next-week", label: translate("settings.defaults.options.nextWeek") },
|
||||
],
|
||||
getValue: () => plugin.settings.taskCreationDefaults.defaultScheduledDate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskCreationDefaults.defaultScheduledDate = value as any;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// Reminder Defaults Section
|
||||
createSectionHeader(container, translate("settings.defaults.header.defaultReminders"));
|
||||
createHelpText(container, translate("settings.defaults.description.defaultReminders"));
|
||||
|
||||
// Reminder list - using card layout
|
||||
const remindersContainer = container.createDiv("tasknotes-reminders-container");
|
||||
renderRemindersList(remindersContainer, plugin, save, translate);
|
||||
|
||||
// Add reminder button
|
||||
new Setting(container)
|
||||
.setName(translate("settings.defaults.reminders.addReminder.name"))
|
||||
.setDesc(translate("settings.defaults.reminders.addReminder.description"))
|
||||
.addButton((button) =>
|
||||
button
|
||||
.setButtonText(translate("settings.defaults.reminders.addReminder.buttonText"))
|
||||
.onClick(async () => {
|
||||
const newId = `reminder_${Date.now()}`;
|
||||
const newReminder = {
|
||||
id: newId,
|
||||
type: "relative" as const,
|
||||
relatedTo: "due" as const,
|
||||
offset: 1,
|
||||
unit: "hours" as const,
|
||||
direction: "before" as const,
|
||||
description: "Reminder",
|
||||
};
|
||||
plugin.settings.taskCreationDefaults.defaultReminders =
|
||||
plugin.settings.taskCreationDefaults.defaultReminders || [];
|
||||
plugin.settings.taskCreationDefaults.defaultReminders.push(newReminder);
|
||||
save();
|
||||
renderRemindersList(remindersContainer, plugin, save, translate);
|
||||
})
|
||||
);
|
||||
|
||||
// Template Settings Section
|
||||
createSectionHeader(container, translate("settings.defaults.header.bodyTemplate"));
|
||||
createHelpText(container, translate("settings.defaults.description.bodyTemplate"));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.defaults.bodyTemplate.useBodyTemplate.name"),
|
||||
desc: translate("settings.defaults.bodyTemplate.useBodyTemplate.description"),
|
||||
getValue: () => plugin.settings.taskCreationDefaults.useBodyTemplate,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.taskCreationDefaults.useBodyTemplate = value;
|
||||
save();
|
||||
// Re-render to show/hide template path
|
||||
renderDefaultsTab(container, plugin, save);
|
||||
},
|
||||
});
|
||||
|
||||
if (plugin.settings.taskCreationDefaults.useBodyTemplate) {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.defaults.bodyTemplate.bodyTemplateFile.name"),
|
||||
desc: translate("settings.defaults.bodyTemplate.bodyTemplateFile.description"),
|
||||
placeholder: translate("settings.defaults.bodyTemplate.bodyTemplateFile.placeholder"),
|
||||
getValue: () => plugin.settings.taskCreationDefaults.bodyTemplate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskCreationDefaults.bodyTemplate = value;
|
||||
save();
|
||||
},
|
||||
ariaLabel: translate("settings.defaults.bodyTemplate.bodyTemplateFile.ariaLabel"),
|
||||
});
|
||||
}
|
||||
|
||||
// Template Variables Help
|
||||
if (plugin.settings.taskCreationDefaults.useBodyTemplate) {
|
||||
const helpContainer = container.createDiv("tasknotes-settings__help-section");
|
||||
helpContainer.createEl("h4", {
|
||||
text: translate("settings.defaults.bodyTemplate.variablesHeader"),
|
||||
});
|
||||
const helpList = helpContainer.createEl("ul");
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.title"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.details"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.date"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.time"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.priority"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.status"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.contexts"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.tags"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.projects"),
|
||||
});
|
||||
}
|
||||
|
||||
// Instant Conversion Section
|
||||
createSectionHeader(container, translate("settings.defaults.header.instantTaskConversion"));
|
||||
createHelpText(container, translate("settings.defaults.description.instantTaskConversion"));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.defaults.instantConversion.useDefaultsOnInstantConvert.name"),
|
||||
desc: translate(
|
||||
"settings.defaults.instantConversion.useDefaultsOnInstantConvert.description"
|
||||
),
|
||||
getValue: () => plugin.settings.useDefaultsOnInstantConvert,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.useDefaultsOnInstantConvert = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function renderDefaultProjectsList(
|
||||
container: HTMLElement,
|
||||
plugin: TaskNotesPlugin,
|
||||
save: () => void,
|
||||
selectedFiles: TAbstractFile[],
|
||||
translate: (key: TranslationKey, params?: Record<string, string | number>) => string
|
||||
): void {
|
||||
// Remove existing projects list
|
||||
const existingList = container.querySelector(".default-projects-list");
|
||||
if (existingList) {
|
||||
existingList.remove();
|
||||
}
|
||||
|
||||
if (selectedFiles.length === 0) return;
|
||||
|
||||
const projectsList = container.createDiv("default-projects-list");
|
||||
selectedFiles.forEach((file) => {
|
||||
createCard(projectsList, {
|
||||
id: file.path,
|
||||
collapsible: true,
|
||||
defaultCollapsed: true,
|
||||
header: {
|
||||
primaryText: file.name.replace(/\.md$/, ""),
|
||||
actions: [
|
||||
createDeleteHeaderButton(
|
||||
() => {
|
||||
const index = selectedFiles.indexOf(file);
|
||||
if (index > -1) {
|
||||
selectedFiles.splice(index, 1);
|
||||
const projectLinks = selectedFiles
|
||||
.map((f) => `[[${f.path.replace(/\.md$/, "")}]]`)
|
||||
.join(", ");
|
||||
plugin.settings.taskCreationDefaults.defaultProjects = projectLinks;
|
||||
save();
|
||||
renderDefaultProjectsList(
|
||||
container,
|
||||
plugin,
|
||||
save,
|
||||
selectedFiles,
|
||||
translate
|
||||
);
|
||||
}
|
||||
},
|
||||
translate("settings.defaults.basicDefaults.defaultProjects.removeTooltip", {
|
||||
name: file.name,
|
||||
})
|
||||
),
|
||||
],
|
||||
},
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function renderRelativeReminderConfig(
|
||||
reminder: DefaultReminder,
|
||||
updateItem: (updates: Partial<DefaultReminder>) => void,
|
||||
translate: (key: TranslationKey, params?: Record<string, string | number>) => string
|
||||
): CardRow[] {
|
||||
const offsetInput = createCardNumberInput(0, undefined, 1, reminder.offset);
|
||||
offsetInput.addEventListener("input", () => {
|
||||
const offset = parseInt(offsetInput.value);
|
||||
if (!isNaN(offset) && offset >= 0) {
|
||||
updateItem({ offset });
|
||||
}
|
||||
});
|
||||
|
||||
const unitSelect = createCardSelect(
|
||||
[
|
||||
{ value: "minutes", label: translate("settings.defaults.reminders.units.minutes") },
|
||||
{ value: "hours", label: translate("settings.defaults.reminders.units.hours") },
|
||||
{ value: "days", label: translate("settings.defaults.reminders.units.days") },
|
||||
],
|
||||
reminder.unit
|
||||
);
|
||||
unitSelect.addEventListener("change", () => {
|
||||
updateItem({ unit: unitSelect.value as any });
|
||||
});
|
||||
|
||||
const directionSelect = createCardSelect(
|
||||
[
|
||||
{ value: "before", label: translate("settings.defaults.reminders.directions.before") },
|
||||
{ value: "after", label: translate("settings.defaults.reminders.directions.after") },
|
||||
],
|
||||
reminder.direction
|
||||
);
|
||||
directionSelect.addEventListener("change", () => {
|
||||
updateItem({ direction: directionSelect.value as any });
|
||||
});
|
||||
|
||||
const relatedToSelect = createCardSelect(
|
||||
[
|
||||
{ value: "due", label: translate("settings.defaults.reminders.relatedTo.due") },
|
||||
{
|
||||
value: "scheduled",
|
||||
label: translate("settings.defaults.reminders.relatedTo.scheduled"),
|
||||
},
|
||||
],
|
||||
reminder.relatedTo
|
||||
);
|
||||
relatedToSelect.addEventListener("change", () => {
|
||||
updateItem({ relatedTo: relatedToSelect.value as any });
|
||||
});
|
||||
|
||||
return [
|
||||
{ label: translate("settings.defaults.reminders.fields.offset"), input: offsetInput },
|
||||
{ label: translate("settings.defaults.reminders.fields.unit"), input: unitSelect },
|
||||
{
|
||||
label: translate("settings.defaults.reminders.fields.direction"),
|
||||
input: directionSelect,
|
||||
},
|
||||
{
|
||||
label: translate("settings.defaults.reminders.fields.relatedTo"),
|
||||
input: relatedToSelect,
|
||||
},
|
||||
];
|
||||
}
|
||||
|
||||
function renderAbsoluteReminderConfig(
|
||||
reminder: DefaultReminder,
|
||||
updateItem: (updates: Partial<DefaultReminder>) => void,
|
||||
translate: (key: TranslationKey, params?: Record<string, string | number>) => string
|
||||
): CardRow[] {
|
||||
const dateInput = createCardInput(
|
||||
"date",
|
||||
reminder.absoluteDate || new Date().toISOString().split("T")[0]
|
||||
);
|
||||
dateInput.addEventListener("input", () => {
|
||||
updateItem({ absoluteDate: dateInput.value });
|
||||
});
|
||||
|
||||
const timeInput = createCardInput("time", reminder.absoluteTime || "09:00");
|
||||
timeInput.addEventListener("input", () => {
|
||||
updateItem({ absoluteTime: timeInput.value });
|
||||
});
|
||||
|
||||
return [
|
||||
{ label: translate("settings.defaults.reminders.fields.date"), input: dateInput },
|
||||
{ label: translate("settings.defaults.reminders.fields.time"), input: timeInput },
|
||||
];
|
||||
}
|
||||
|
||||
function renderRemindersList(
|
||||
container: HTMLElement,
|
||||
plugin: TaskNotesPlugin,
|
||||
save: () => void,
|
||||
translate: (key: TranslationKey, params?: Record<string, string | number>) => string
|
||||
): void {
|
||||
container.empty();
|
||||
|
||||
if (
|
||||
!plugin.settings.taskCreationDefaults.defaultReminders ||
|
||||
plugin.settings.taskCreationDefaults.defaultReminders.length === 0
|
||||
) {
|
||||
showCardEmptyState(
|
||||
container,
|
||||
translate("settings.defaults.reminders.emptyState"),
|
||||
translate("settings.defaults.reminders.emptyStateButton"),
|
||||
() => {
|
||||
// Trigger the add reminder button
|
||||
const addReminderButton = document.querySelector(
|
||||
'[data-setting-name="Add default reminder"] button'
|
||||
);
|
||||
if (addReminderButton) {
|
||||
(addReminderButton as HTMLElement).click();
|
||||
}
|
||||
}
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.settings.taskCreationDefaults.defaultReminders.forEach((reminder, index) => {
|
||||
const timingText = formatReminderTiming(reminder, translate);
|
||||
|
||||
const descInput = createCardInput(
|
||||
"text",
|
||||
translate("settings.defaults.reminders.reminderDescription"),
|
||||
reminder.description
|
||||
);
|
||||
|
||||
const typeSelect = createCardSelect(
|
||||
[
|
||||
{
|
||||
value: "relative",
|
||||
label: translate("settings.defaults.reminders.types.relative"),
|
||||
},
|
||||
{
|
||||
value: "absolute",
|
||||
label: translate("settings.defaults.reminders.types.absolute"),
|
||||
},
|
||||
],
|
||||
reminder.type
|
||||
);
|
||||
|
||||
const updateCallback = (updates: Partial<DefaultReminder>) => {
|
||||
Object.assign(reminder, updates);
|
||||
save();
|
||||
const card = container.querySelector(`[data-card-id="${reminder.id}"]`);
|
||||
if (card) {
|
||||
const secondaryText = card.querySelector(
|
||||
".tasknotes-settings__card-secondary-text"
|
||||
);
|
||||
if (secondaryText) {
|
||||
secondaryText.textContent = formatReminderTiming(reminder, translate);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const configRows =
|
||||
reminder.type === "relative"
|
||||
? renderRelativeReminderConfig(reminder, updateCallback, translate)
|
||||
: renderAbsoluteReminderConfig(reminder, updateCallback, translate);
|
||||
|
||||
const card = createCard(container, {
|
||||
id: reminder.id,
|
||||
collapsible: true,
|
||||
defaultCollapsed: true,
|
||||
header: {
|
||||
primaryText:
|
||||
reminder.description ||
|
||||
translate("settings.defaults.reminders.unnamedReminder"),
|
||||
secondaryText: timingText,
|
||||
actions: [
|
||||
createDeleteHeaderButton(() => {
|
||||
plugin.settings.taskCreationDefaults.defaultReminders.splice(index, 1);
|
||||
save();
|
||||
renderRemindersList(container, plugin, save, translate);
|
||||
}, translate("settings.defaults.reminders.deleteTooltip")),
|
||||
],
|
||||
},
|
||||
content: {
|
||||
sections: [
|
||||
{
|
||||
rows: [
|
||||
{
|
||||
label: translate("settings.defaults.reminders.fields.description"),
|
||||
input: descInput,
|
||||
},
|
||||
{
|
||||
label: translate("settings.defaults.reminders.fields.type"),
|
||||
input: typeSelect,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
rows: configRows,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
descInput.addEventListener("input", () => {
|
||||
reminder.description = descInput.value;
|
||||
save();
|
||||
const primaryText = card.querySelector(".tasknotes-settings__card-primary-text");
|
||||
if (primaryText) {
|
||||
primaryText.textContent =
|
||||
reminder.description ||
|
||||
translate("settings.defaults.reminders.unnamedReminder");
|
||||
}
|
||||
});
|
||||
|
||||
typeSelect.addEventListener("change", () => {
|
||||
reminder.type = typeSelect.value as any;
|
||||
save();
|
||||
|
||||
// Re-render only the config rows
|
||||
const newConfigRows =
|
||||
reminder.type === "relative"
|
||||
? renderRelativeReminderConfig(reminder, updateCallback, translate)
|
||||
: renderAbsoluteReminderConfig(reminder, updateCallback, translate);
|
||||
|
||||
const content = card.querySelector(".tasknotes-settings__card-content");
|
||||
if (content && content.children[1]) {
|
||||
const newSection = document.createElement("div");
|
||||
newConfigRows.forEach((row) => {
|
||||
const configRow = newSection.createDiv("tasknotes-settings__card-config-row");
|
||||
if (row.fullWidth) {
|
||||
configRow.style.flexDirection = "column";
|
||||
configRow.style.alignItems = "flex-start";
|
||||
configRow.style.gap = "0.5rem";
|
||||
}
|
||||
const label = configRow.createSpan("tasknotes-settings__card-config-label");
|
||||
label.textContent = row.label;
|
||||
configRow.appendChild(row.input);
|
||||
});
|
||||
content.replaceChild(newSection, content.children[1]);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function formatReminderTiming(
|
||||
reminder: DefaultReminder,
|
||||
translate: (key: TranslationKey, params?: Record<string, string | number>) => string
|
||||
): string {
|
||||
if (reminder.type === "relative") {
|
||||
const direction =
|
||||
reminder.direction === "before"
|
||||
? translate("settings.defaults.reminders.directions.before")
|
||||
: translate("settings.defaults.reminders.directions.after");
|
||||
const unit = translate(
|
||||
`settings.defaults.reminders.units.${reminder.unit || "hours"}` as TranslationKey
|
||||
);
|
||||
const offset = reminder.offset ?? 1;
|
||||
const relatedTo =
|
||||
reminder.relatedTo === "due"
|
||||
? translate("settings.defaults.reminders.relatedTo.due")
|
||||
: translate("settings.defaults.reminders.relatedTo.scheduled");
|
||||
return `${offset} ${unit} ${direction} ${relatedTo}`;
|
||||
} else {
|
||||
const date = reminder.absoluteDate || translate("settings.defaults.reminders.fields.date");
|
||||
const time = reminder.absoluteTime || translate("settings.defaults.reminders.fields.time");
|
||||
return `${date} at ${time}`;
|
||||
}
|
||||
}
|
||||
|
|
@ -7,7 +7,6 @@ import {
|
|||
createDropdownSetting,
|
||||
createNumberSetting,
|
||||
createHelpText,
|
||||
// createButtonSetting
|
||||
} from "../components/settingHelpers";
|
||||
import { showStorageLocationConfirmationModal } from "../../modals/StorageLocationConfirmationModal";
|
||||
import { getAvailableLanguages } from "../../locales";
|
||||
|
|
@ -146,204 +145,85 @@ export function renderFeaturesTab(
|
|||
},
|
||||
});
|
||||
|
||||
// Deprecated: Old status trigger setting (kept for backward compatibility during migration)
|
||||
// New trigger configuration UI below replaces this
|
||||
|
||||
// NLP Triggers Configuration
|
||||
createSectionHeader(container, "NLP Triggers");
|
||||
createHelpText(
|
||||
container,
|
||||
"Configure trigger characters or strings for each property type. When you type a trigger followed by text, autocomplete suggestions will appear."
|
||||
);
|
||||
|
||||
// Helper function to render a trigger setting
|
||||
const renderTriggerSetting = (
|
||||
propertyId: string,
|
||||
displayName: string,
|
||||
description: string,
|
||||
defaultTrigger: string
|
||||
) => {
|
||||
const triggerConfig = plugin.settings.nlpTriggers.triggers.find(
|
||||
(t) => t.propertyId === propertyId
|
||||
);
|
||||
const currentTrigger = triggerConfig?.trigger || defaultTrigger;
|
||||
const isEnabled = triggerConfig?.enabled ?? true;
|
||||
|
||||
const setting = new Setting(container)
|
||||
.setName(displayName)
|
||||
.setDesc(description)
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(isEnabled).onChange(async (enabled) => {
|
||||
// Update or create trigger config
|
||||
const index = plugin.settings.nlpTriggers.triggers.findIndex(
|
||||
(t) => t.propertyId === propertyId
|
||||
);
|
||||
if (index !== -1) {
|
||||
plugin.settings.nlpTriggers.triggers[index].enabled = enabled;
|
||||
} else {
|
||||
plugin.settings.nlpTriggers.triggers.push({
|
||||
propertyId,
|
||||
trigger: defaultTrigger,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
save();
|
||||
// Re-render to show/hide trigger input
|
||||
renderFeaturesTab(container, plugin, save);
|
||||
});
|
||||
});
|
||||
|
||||
if (isEnabled) {
|
||||
setting.addText((text) => {
|
||||
text.setValue(currentTrigger)
|
||||
.setPlaceholder(defaultTrigger)
|
||||
.onChange(async (value) => {
|
||||
// Validate trigger (allow trailing/leading spaces for triggers like "def: ")
|
||||
if (value.trim().length === 0) {
|
||||
new Notice("Trigger cannot be empty");
|
||||
return;
|
||||
}
|
||||
if (value.length > 10) {
|
||||
new Notice("Trigger is too long (max 10 characters)");
|
||||
return;
|
||||
}
|
||||
|
||||
// Update trigger (use raw value, don't trim)
|
||||
const index = plugin.settings.nlpTriggers.triggers.findIndex(
|
||||
(t) => t.propertyId === propertyId
|
||||
);
|
||||
if (index !== -1) {
|
||||
plugin.settings.nlpTriggers.triggers[index].trigger = value;
|
||||
} else {
|
||||
plugin.settings.nlpTriggers.triggers.push({
|
||||
propertyId,
|
||||
trigger: value,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
save();
|
||||
});
|
||||
text.inputEl.style.width = "100px";
|
||||
});
|
||||
|
||||
// Add special note for tags trigger
|
||||
if (propertyId === "tags" && currentTrigger !== "#") {
|
||||
setting.descEl.createDiv({
|
||||
text: "⚠️ Using custom tag trigger - Obsidian's native tag suggester will be disabled.",
|
||||
cls: "setting-item-description",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Built-in triggers
|
||||
renderTriggerSetting(
|
||||
"tags",
|
||||
"Tags Trigger",
|
||||
"Trigger for #tags. When set to '#', uses Obsidian's native tag suggester.",
|
||||
"#"
|
||||
);
|
||||
|
||||
renderTriggerSetting(
|
||||
"contexts",
|
||||
"Contexts Trigger",
|
||||
"Trigger for @contexts. Type this character followed by a context name.",
|
||||
"@"
|
||||
);
|
||||
|
||||
renderTriggerSetting(
|
||||
"projects",
|
||||
"Projects Trigger",
|
||||
"Trigger for +projects. Supports wikilinks for multi-word projects.",
|
||||
"+"
|
||||
);
|
||||
|
||||
renderTriggerSetting(
|
||||
"status",
|
||||
"Status Trigger",
|
||||
"Trigger for status suggestions. Type this to see available statuses.",
|
||||
"*"
|
||||
);
|
||||
|
||||
renderTriggerSetting(
|
||||
"priority",
|
||||
"Priority Trigger",
|
||||
"Trigger for priority suggestions. Disabled by default (priority uses keyword matching).",
|
||||
"!"
|
||||
);
|
||||
|
||||
// User-defined field triggers
|
||||
if (plugin.settings.userFields && plugin.settings.userFields.length > 0) {
|
||||
createHelpText(
|
||||
container,
|
||||
"User-Defined Fields: Configure triggers for your custom fields below."
|
||||
);
|
||||
|
||||
for (const userField of plugin.settings.userFields) {
|
||||
const triggerConfig = plugin.settings.nlpTriggers.triggers.find(
|
||||
(t) => t.propertyId === userField.id
|
||||
);
|
||||
const isEnabled = triggerConfig?.enabled ?? false;
|
||||
const currentTrigger = triggerConfig?.trigger || `${userField.id}:`;
|
||||
|
||||
const setting = new Setting(container)
|
||||
.setName(`${userField.displayName} Trigger`)
|
||||
.setDesc(`Trigger for custom field "${userField.displayName}" (${userField.type})`)
|
||||
.addToggle((toggle) => {
|
||||
toggle.setValue(isEnabled).onChange(async (enabled) => {
|
||||
const index = plugin.settings.nlpTriggers.triggers.findIndex(
|
||||
(t) => t.propertyId === userField.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
plugin.settings.nlpTriggers.triggers[index].enabled = enabled;
|
||||
} else {
|
||||
plugin.settings.nlpTriggers.triggers.push({
|
||||
propertyId: userField.id,
|
||||
trigger: currentTrigger,
|
||||
enabled,
|
||||
});
|
||||
}
|
||||
save();
|
||||
renderFeaturesTab(container, plugin, save);
|
||||
});
|
||||
});
|
||||
|
||||
if (isEnabled) {
|
||||
setting.addText((text) => {
|
||||
text.setValue(currentTrigger)
|
||||
.setPlaceholder(`${userField.id}:`)
|
||||
.onChange(async (value) => {
|
||||
// Validate trigger (allow trailing/leading spaces)
|
||||
if (value.trim().length === 0) {
|
||||
new Notice("Trigger cannot be empty");
|
||||
return;
|
||||
}
|
||||
if (value.length > 10) {
|
||||
new Notice("Trigger is too long (max 10 characters)");
|
||||
return;
|
||||
}
|
||||
|
||||
const index = plugin.settings.nlpTriggers.triggers.findIndex(
|
||||
(t) => t.propertyId === userField.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
plugin.settings.nlpTriggers.triggers[index].trigger = value;
|
||||
} else {
|
||||
plugin.settings.nlpTriggers.triggers.push({
|
||||
propertyId: userField.id,
|
||||
trigger: value,
|
||||
enabled: true,
|
||||
});
|
||||
}
|
||||
save();
|
||||
});
|
||||
text.inputEl.style.width = "100px";
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// Note: NLP triggers are now configured per-property in the Task Properties tab
|
||||
}
|
||||
|
||||
// Task Creation Section (Body Templates)
|
||||
createSectionHeader(container, translate("settings.defaults.header.bodyTemplate"));
|
||||
createHelpText(container, translate("settings.defaults.description.bodyTemplate"));
|
||||
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.defaults.bodyTemplate.useBodyTemplate.name"),
|
||||
desc: translate("settings.defaults.bodyTemplate.useBodyTemplate.description"),
|
||||
getValue: () => plugin.settings.taskCreationDefaults.useBodyTemplate,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.taskCreationDefaults.useBodyTemplate = value;
|
||||
save();
|
||||
// Re-render to show/hide template path
|
||||
renderFeaturesTab(container, plugin, save);
|
||||
},
|
||||
});
|
||||
|
||||
if (plugin.settings.taskCreationDefaults.useBodyTemplate) {
|
||||
createTextSetting(container, {
|
||||
name: translate("settings.defaults.bodyTemplate.bodyTemplateFile.name"),
|
||||
desc: translate("settings.defaults.bodyTemplate.bodyTemplateFile.description"),
|
||||
placeholder: translate("settings.defaults.bodyTemplate.bodyTemplateFile.placeholder"),
|
||||
getValue: () => plugin.settings.taskCreationDefaults.bodyTemplate,
|
||||
setValue: async (value: string) => {
|
||||
plugin.settings.taskCreationDefaults.bodyTemplate = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// Template Variables Help
|
||||
const helpContainer = container.createDiv("tasknotes-settings__help-section");
|
||||
helpContainer.createEl("h4", {
|
||||
text: translate("settings.defaults.bodyTemplate.variablesHeader"),
|
||||
});
|
||||
const helpList = helpContainer.createEl("ul");
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.title"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.details"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.date"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.time"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.priority"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.status"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.contexts"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.tags"),
|
||||
});
|
||||
helpList.createEl("li", {
|
||||
text: translate("settings.defaults.bodyTemplate.variables.projects"),
|
||||
});
|
||||
}
|
||||
|
||||
// Instant Conversion Defaults
|
||||
createToggleSetting(container, {
|
||||
name: translate("settings.defaults.instantConversion.useDefaultsOnInstantConvert.name"),
|
||||
desc: translate(
|
||||
"settings.defaults.instantConversion.useDefaultsOnInstantConvert.description"
|
||||
),
|
||||
getValue: () => plugin.settings.useDefaultsOnInstantConvert,
|
||||
setValue: async (value: boolean) => {
|
||||
plugin.settings.useDefaultsOnInstantConvert = value;
|
||||
save();
|
||||
},
|
||||
});
|
||||
|
||||
// Pomodoro Timer Section
|
||||
createSectionHeader(container, translate("settings.features.pomodoro.header"));
|
||||
createHelpText(container, translate("settings.features.pomodoro.description"));
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -69,7 +69,7 @@
|
|||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 6px;
|
||||
margin-bottom: 12px;
|
||||
overflow: hidden;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.tasknotes-settings__card:hover {
|
||||
|
|
@ -140,6 +140,7 @@
|
|||
.tasknotes-settings__card-config-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
|
|
@ -178,7 +179,8 @@ select.tasknotes-settings__card-input {
|
|||
.tasknotes-settings__card-input[type="text"],
|
||||
.tasknotes-settings__card-input[type="url"],
|
||||
.tasknotes-settings__card-input[type="email"] {
|
||||
flex: 1;
|
||||
flex: 0 1 auto;
|
||||
min-width: 150px;
|
||||
}
|
||||
|
||||
.tasknotes-settings__card-input[type="checkbox"] {
|
||||
|
|
@ -262,7 +264,7 @@ select.tasknotes-settings__card-input {
|
|||
.tasknotes-settings__card-header-btn {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
border: none !important;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: var(--cursor);
|
||||
|
|
@ -270,6 +272,15 @@ select.tasknotes-settings__card-input {
|
|||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.tasknotes-settings__card-header-btn:focus {
|
||||
box-shadow: none !important;
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.tasknotes-settings__card-header-btn:hover {
|
||||
|
|
@ -312,6 +323,65 @@ select.tasknotes-settings__card-input {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Selected Project Items (chips) */
|
||||
.default-projects-list-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.tasknotes-settings__project-item {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
padding: 4px 8px 4px 12px;
|
||||
background: var(--background-secondary);
|
||||
border: 1px solid var(--background-modifier-border);
|
||||
border-radius: 16px;
|
||||
font-size: 12px;
|
||||
color: var(--text-normal);
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.tasknotes-settings__project-item:hover {
|
||||
background: var(--background-modifier-hover);
|
||||
border-color: var(--background-modifier-border-hover);
|
||||
}
|
||||
|
||||
.tasknotes-settings__project-item span {
|
||||
max-width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.tasknotes-settings__project-item button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
padding: 0;
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
background: transparent;
|
||||
color: var(--text-muted);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.tasknotes-settings__project-item button:hover {
|
||||
background: var(--background-modifier-error);
|
||||
color: var(--text-on-accent);
|
||||
}
|
||||
|
||||
.tasknotes-settings__project-item button svg {
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
}
|
||||
|
||||
/* Drag Handle */
|
||||
.tasknotes-settings__card-drag-handle {
|
||||
position: absolute;
|
||||
|
|
@ -935,10 +1005,9 @@ select.tasknotes-settings__card-input {
|
|||
}
|
||||
|
||||
.tasknotes-settings__collapsible-section-content {
|
||||
max-height: 500px;
|
||||
max-height: none;
|
||||
opacity: 1;
|
||||
overflow: visible;
|
||||
transition: max-height 0.3s ease, opacity 0.2s ease, padding 0.2s ease;
|
||||
}
|
||||
|
||||
/* Collapsed state */
|
||||
|
|
|
|||
Loading…
Reference in a new issue