fix(#1419): Custom statuses and priorities now save correctly in settings

Text input fields used 'change' event which only fires on blur, so values
were lost if modal closed without blurring. Changed to 'input' event for
immediate value capture, and added hide() method to flush pending debounced
saves when settings modal closes.
This commit is contained in:
callumalpass 2026-01-04 10:03:13 +11:00
parent 62eb7d0e53
commit 6f917b1eef
5 changed files with 53 additions and 9 deletions

View file

@ -52,3 +52,9 @@ Example:
- Updated `TAG_PATTERN` to include hyphens: `/#[\w/-]+/g`
- Thanks to @JerryLu086 for reporting
- (#1419) Fixed custom statuses and priorities not saving in settings
- Text input fields used `change` event which only fires on blur, losing values if modal closed without blurring
- Changed to `input` event so values are captured on every keystroke
- Added `hide()` method to settings tab to flush pending debounced saves when modal closes
- Thanks to @s33a for reporting

View file

@ -1,6 +1,6 @@
import { App, PluginSettingTab, Platform, requireApiVersion } from "obsidian";
import TaskNotesPlugin from "../main";
import { debounce } from "./components/settingHelpers";
import { debounce, DebouncedFunction } from "./components/settingHelpers";
import { renderGeneralTab } from "./tabs/generalTab";
import { renderTaskPropertiesTab } from "./tabs/taskPropertiesTab";
import { renderModalFieldsTab } from "./tabs/modalFieldsTab";
@ -19,7 +19,10 @@ export class TaskNotesSettingTab extends PluginSettingTab {
plugin: TaskNotesPlugin;
private activeTab = "general";
private tabContents: Record<string, HTMLElement> = {};
private debouncedSave = debounce(() => this.plugin.saveSettings(), 500);
private debouncedSave: DebouncedFunction<() => Promise<void>> = debounce(
() => this.plugin.saveSettings(),
500
);
constructor(app: App, plugin: TaskNotesPlugin) {
super(app, plugin);
@ -252,4 +255,12 @@ export class TaskNotesSettingTab extends PluginSettingTab {
// Currently: ICS subscriptions and plugin integrations work on mobile
return true; // ICS subscriptions are always available
}
/**
* Called when the settings tab is hidden/closed.
* Flushes any pending debounced saves to ensure settings are persisted.
*/
hide(): void {
this.debouncedSave.flush();
}
}

View file

@ -365,20 +365,36 @@ export function createListHeaders(
return headersRow;
}
/**
* Debounced function interface with flush capability
*/
export interface DebouncedFunction<T extends (...args: any[]) => any> {
(...args: Parameters<T>): void;
/** Immediately execute any pending debounced call */
flush: () => void;
}
/**
* Debounce function for reducing save calls
* Returns a debounced function with a flush() method to immediately execute pending calls
*/
export function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number,
immediate = false
): (...args: Parameters<T>) => void {
): DebouncedFunction<T> {
let timeout: ReturnType<typeof setTimeout> | undefined;
let lastArgs: Parameters<T> | undefined;
let lastThis: any;
const debounced = function (this: any, ...args: Parameters<T>) {
lastArgs = args;
lastThis = this;
return function (this: any, ...args: Parameters<T>) {
const later = () => {
timeout = undefined;
if (!immediate) func.apply(this, args);
lastArgs = undefined;
if (!immediate) func.apply(lastThis, args);
};
const callNow = immediate && !timeout;
@ -386,5 +402,16 @@ export function debounce<T extends (...args: any[]) => any>(
timeout = setTimeout(later, wait);
if (callNow) func.apply(this, args);
} as DebouncedFunction<T>;
debounced.flush = () => {
if (timeout && lastArgs) {
clearTimeout(timeout);
timeout = undefined;
func.apply(lastThis, lastArgs);
lastArgs = undefined;
}
};
return debounced;
}

View file

@ -270,13 +270,13 @@ function renderPriorityList(
},
});
valueInput.addEventListener("change", () => {
valueInput.addEventListener("input", () => {
priority.value = valueInput.value;
save();
if (onPrioritiesChanged) onPrioritiesChanged();
});
labelInput.addEventListener("change", () => {
labelInput.addEventListener("input", () => {
priority.label = labelInput.value;
card.querySelector(".tasknotes-settings__card-primary-text")!.textContent =
priority.label || priority.value || "untitled";

View file

@ -344,7 +344,7 @@ function renderStatusList(
statusCard = createCard(container, cardConfig);
updateDelayInputVisibility();
valueInput.addEventListener("change", () => {
valueInput.addEventListener("input", () => {
status.value = valueInput.value;
statusCard.querySelector(".tasknotes-settings__card-primary-text")!.textContent =
status.value || "untitled";
@ -352,7 +352,7 @@ function renderStatusList(
if (onStatusesChanged) onStatusesChanged();
});
labelInput.addEventListener("change", () => {
labelInput.addEventListener("input", () => {
status.label = labelInput.value;
statusCard.querySelector(".tasknotes-settings__card-secondary-text")!.textContent =
status.label || "No label";