Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs/releases/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@ When a change has user-facing documentation, include a canonical tasknotes.dev l

## Added

- (#2436, #909) Added an opt-in setting to use default projects only when no project is specified, so selecting a project or entering one with NLP can replace the defaults. Existing additive behavior remains unchanged by default. See [Projects settings](https://tasknotes.dev/settings/task-properties/#projects). Thanks to @RumiaKitinari for the suggestion.

- (#1955) Added Italian interface translations, localized dates and count-aware messages in bundled builds and downloadable language packs. UI language and NLP language remain independent settings.
- Thanks to @starise for contributing and reviewing the Italian translation.

Expand Down
1 change: 1 addition & 0 deletions docs/settings/task-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,7 @@ Projects the task belongs to. Configuration options:

- **Property key**: Frontmatter field name (default: `projects`)
- **Default projects**: Select project notes to automatically link to new tasks
- **Use default projects only when no project is specified**: Off by default. When enabled, choosing a project in the creation form or filling the form from natural-language input replaces the preloaded default projects. Instant conversion also omits defaults when the input specifies a project. Explicit selections and projects supplied by a Bases view or parent task are preserved. Clearing projects leaves the selection empty; it does not restore the defaults.
- **Use active note for new tasks**: Automatically link the active note as a project when opening task creation from the command palette or ribbon
- **Use parent note for inline/instant conversion**: Automatically link the source note as a project when using inline task creation or instant task conversion
- **NLP trigger**: Character that triggers project parsing (default: `+`)
Expand Down
66 changes: 66 additions & 0 deletions e2e/issues/issue-2436-default-project-fallback.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { test, expect } from "@playwright/test";
import { launchObsidian, closeObsidian, type ObsidianApp } from "../obsidian";

let instance: ObsidianApp;
let original: any;
const prefix = `Issue2436-${Date.now()}`;

test.beforeAll(async () => {
instance = await launchObsidian();
original = await instance.page.evaluate(async (prefix) => {
const app = (window as any).app;
const plugin = app.plugins.plugins.tasknotes;
const original = JSON.parse(JSON.stringify(plugin.settings));
await app.vault.create(`${prefix}-Homework.md`, "# Homework");
await app.vault.create(`${prefix}-Personal.md`, "# Personal");
plugin.settings.enableNaturalLanguageInput = true;
plugin.settings.nlpEnterToSubmit = false;
plugin.settings.openTaskAfterCreation = "none";
plugin.settings.taskCreationDefaults.defaultProjects = `[[${prefix}-Homework]]`;
return original;
}, prefix);
});

test.afterAll(async () => {
if (!instance) return;
try {
await instance.page.evaluate(async ({ original, prefix }) => {
const app = (window as any).app;
app.plugins.plugins.tasknotes.settings = original;
for (const file of app.vault.getMarkdownFiles()) {
if (file.basename.startsWith(prefix)) await app.vault.delete(file);
}
}, { original, prefix });
} finally {
await closeObsidian(instance);
}
});

for (const [name, enabled, explicit] of [
["legacy additive", false, true],
["fallback override", true, true],
["fallback without explicit project", true, false],
] as const) {
test(name, async () => {
const title = `${prefix}-${name}`;
await instance.page.evaluate((enabled) => {
const plugin = (window as any).app.plugins.plugins.tasknotes;
plugin.settings.taskCreationDefaults.defaultProjectsOnlyWhenEmpty = enabled;
plugin.openTaskCreationModal();
}, enabled);
const editor = instance.page.locator(".modal .tn-task-modal__markdown-editor--nlp .cm-content");
await expect(editor).toBeVisible();
await editor.click();
await instance.page.keyboard.insertText(title + (explicit ? ` +[[${prefix}-Personal]]` : ""));
await instance.page.locator(".modal").getByRole("button", { name: "Save", exact: true }).click();
await expect(editor).toHaveCount(0);
const expected = explicit
? enabled ? [`[[${prefix}-Personal]]`] : [`[[${prefix}-Homework]]`, `[[${prefix}-Personal]]`]
: [`[[${prefix}-Homework]]`];
await expect.poll(() => instance.page.evaluate((title) => {
const app = (window as any).app;
const file = app.vault.getMarkdownFiles().find((file: any) => file.basename === title);
return file ? app.metadataCache.getFileCache(file)?.frontmatter?.projects : null;
}, title)).toEqual(expected);
});
}
5 changes: 5 additions & 0 deletions src/i18n/locales/de/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13493,6 +13493,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "Inhaltsvorlage verwenden"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "Standardprojekte nur verwenden, wenn kein Projekt angegeben ist"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13493,6 +13493,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "Use body template"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "Use default projects only when no project is specified"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/es/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13493,6 +13493,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "Usar plantilla del cuerpo"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "Usar los proyectos predeterminados solo cuando no se especifique ningún proyecto"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/fr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13493,6 +13493,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "Utiliser un modèle de contenu"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "Utiliser les projets par défaut uniquement si aucun projet n’est spécifié"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/it/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13492,6 +13492,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "Usa modello per il contenuto dell’attività"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "Usa i progetti predefiniti solo quando non è specificato alcun progetto"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/ja/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13492,6 +13492,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "ボディテンプレートを使用"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "プロジェクトが指定されていない場合のみ既定のプロジェクトを使用"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/ko/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13492,6 +13492,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "본문 템플릿 사용"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "프로젝트가 지정되지 않은 경우에만 기본 프로젝트 사용"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/pt/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13494,6 +13494,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "Usar modelo de corpo"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "Usar os projetos padrão apenas quando nenhum projeto for especificado"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/ru/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13493,6 +13493,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "Использовать шаблон тела"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "Использовать проекты по умолчанию, только если проект не указан"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
5 changes: 5 additions & 0 deletions src/i18n/locales/zh/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -13492,6 +13492,11 @@ msgctxt "settings.defaults.bodyTemplate.useBodyTemplate.name"
msgid "Use body template"
msgstr "使用正文模板"

#: src/settings/tabs/taskProperties/projectsPropertyCard.ts
msgctxt "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty"
msgid "Use default projects only when no project is specified"
msgstr "仅在未指定项目时使用默认项目"

#: src/settings/tabs/integrationsTab.ts
msgctxt "settings.integrations.calendarSubscriptions.useICSEndAsDue.name"
msgid "Use ICS event end time as task due date"
Expand Down
32 changes: 29 additions & 3 deletions src/modals/TaskCreationModal.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { displayError } from "../i18n/runtime";
import { uiText } from "../i18n/runtime";
import { msg } from "@lingui/core/macro";
import { App, Notice, setIcon, setTooltip, TFile } from "obsidian";
import { App, Notice, setIcon, setTooltip, TAbstractFile, TFile } from "obsidian";
import TaskNotesPlugin from "../main";
import { TaskModal } from "./TaskModal";
import type { TaskModalProjectItem } from "./taskModalProjects";
import { TaskInfo } from "../types";
import { sanitizeTags } from "../utils/helpers";
import {
Expand Down Expand Up @@ -106,6 +107,7 @@ function createEmbeddableMarkdownEditor(

export class TaskCreationModal extends TaskModal {
private options: TaskCreationOptions;
private defaultProjectItems = new Set<TaskModalProjectItem>();
private nlParser: NaturalLanguageParser;
private nlInput: HTMLTextAreaElement = undefined as unknown as HTMLTextAreaElement; // Legacy - keeping for compatibility
private nlMarkdownEditor: EmbeddableMarkdownEditor | null = null;
Expand Down Expand Up @@ -620,7 +622,28 @@ export class TaskCreationModal extends TaskModal {
}
}

private clearDefaultProjectsForExplicitSelection(): void {
if (!this.plugin.settings.taskCreationDefaults.defaultProjectsOnlyWhenEmpty) return;
this.selectedProjectItems = this.selectedProjectItems.filter(
(item) => !this.defaultProjectItems.has(item)
);
this.defaultProjectItems.clear();
}

protected addProject(file: TAbstractFile): void {
if (file instanceof TFile) this.clearDefaultProjectsForExplicitSelection();
super.addProject(file);
}

protected addProjectsFromStrings(projects: string[]): void {
if (projects.some((project) => project.trim().length > 0)) {
this.clearDefaultProjectsForExplicitSelection();
}
super.addProjectsFromStrings(projects);
}

async initializeFormData(): Promise<void> {
this.defaultProjectItems.clear();
const formState = buildTaskCreationFormState({
defaultPriority: this.plugin.settings.defaultTaskPriority,
defaultStatus: this.plugin.settings.defaultTaskStatus,
Expand All @@ -643,8 +666,11 @@ export class TaskCreationModal extends TaskModal {
this.reminders = formState.reminders;
this.userFields = formState.userFields;

if (formState.projectStrings.length > 0) {
this.initializeProjectsFromStrings(formState.projectStrings);
this.initializeProjectsFromStrings(formState.projectStrings);
// Only defaults have this provenance. Bases, parent/subtask and caller-supplied
// projects are intentional assignments, even if they name a default project.
if (this.options.prePopulatedValues?.projects === undefined) {
this.defaultProjectItems = new Set(this.selectedProjectItems);
}

this.details = this.normalizeDetails(this.details);
Expand Down
5 changes: 4 additions & 1 deletion src/services/InstantTaskConvertService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,10 @@ export class InstantTaskConvertService {
// Apply default projects if defaults are enabled
if (this.plugin.settings.useDefaultsOnInstantConvert) {
const defaults = this.plugin.settings.taskCreationDefaults;
if (defaults.defaultProjects) {
if (
defaults.defaultProjects &&
!(defaults.defaultProjectsOnlyWhenEmpty && parsedProjects.length > 0)
) {
const defaultProjectsArray = splitListPreservingLinksAndQuotes(
defaults.defaultProjects
);
Expand Down
1 change: 1 addition & 0 deletions src/settings/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ export const DEFAULT_TASK_CREATION_DEFAULTS: TaskCreationDefaults = {
defaultContexts: "",
defaultTags: "",
defaultProjects: "",
defaultProjectsOnlyWhenEmpty: false,
useParentNoteForTaskCreation: false,
useParentNoteAsProject: false,
useParentHeaderAsProject: false,
Expand Down
12 changes: 12 additions & 0 deletions src/settings/tabs/taskProperties/projectsPropertyCard.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,14 @@ export function renderProjectsPropertyCard(
translate
);

const defaultProjectsOnlyWhenEmptyToggle = createCardToggle(
plugin.settings.taskCreationDefaults.defaultProjectsOnlyWhenEmpty,
(value) => {
plugin.settings.taskCreationDefaults.defaultProjectsOnlyWhenEmpty = value;
save();
}
);

const useParentNoteForTaskCreationToggle = createCardToggle(
plugin.settings.taskCreationDefaults.useParentNoteForTaskCreation,
(value) => {
Expand Down Expand Up @@ -160,6 +168,10 @@ export function renderProjectsPropertyCard(
input: nestedContainer,
fullWidth: true,
},
{
label: translate(msg({ message: "Use default projects only when no project is specified", context: "settings.taskProperties.projectsCard.defaultProjectsOnlyWhenEmpty" })),
input: defaultProjectsOnlyWhenEmptyToggle,
},
{
label: translate(
msg({ message: "Use active note for new tasks:", context: "settings.taskProperties.projectsCard.useParentNoteForTaskCreation" })
Expand Down
1 change: 1 addition & 0 deletions src/types/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,7 @@ export interface TaskCreationDefaults {
defaultContexts: string; // Comma-separated list
defaultTags: string; // Comma-separated list
defaultProjects: string; // Comma-separated list of project links
defaultProjectsOnlyWhenEmpty: boolean; // Explicit projects replace creation defaults
useParentNoteForTaskCreation: boolean; // Use the active note as a project for normal task creation
useParentNoteAsProject: boolean; // Use the parent note as a project during inline creation and instant conversion
useParentHeaderAsProject: boolean; // Use the closest markdown heading as a project during instant conversion
Expand Down
Loading
Loading