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: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ Go to Settings > Food Tracker to configure:
- `YYYY-MM-DD-[journal]` → `2025-11-12-journal`
- `dddd YYYY-MM-DD` → `Wednesday 2025-11-12`
- **Goals file**: Specify the path to your nutrition goals file (e.g., "nutrition-goals.md"). The field includes type-ahead file suggestions.
- **Metadata field names**: Customize the frontmatter property names used for storing nutrition totals in daily notes. By default, the plugin uses `ft-` prefixed names (e.g., `ft-calories`, `ft-protein`). You can customize these to match your existing setup or personal preferences (e.g., just `calories` instead of `ft-calories`).
- **Metadata field names**: Choose which nutrition totals to display in daily note properties and customize their field names. Each macro (calories, fats, protein, etc.) has a toggle to enable/disable its display in properties, plus a text field to customize the property name. By default, the plugin uses `ft-` prefixed names (e.g., `ft-calories`, `ft-protein`). You can customize these to match your existing setup or preferences. If you only track certain macros (e.g., just calories and fat), you can disable the others to avoid clutter in your daily notes.

> **Note**: When you change the food or workout tag settings, the plugin will only recognize the new tags. Existing entries will need to be updated if you want them included in calculations.

Expand Down
38 changes: 27 additions & 11 deletions src/FoodTrackerSettingTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ import { App, PluginSettingTab, Setting, moment, normalizePath } from "obsidian"
import type FoodTrackerPlugin from "./FoodTrackerPlugin";
import FolderSuggest from "./FolderSuggest";
import FileSuggest from "./FileSuggest";
import { DEFAULT_FRONTMATTER_FIELD_NAMES, type FrontmatterFieldNames } from "./SettingsService";
import {
DEFAULT_FRONTMATTER_FIELD_NAMES,
type EnabledFrontmatterFields,
type FrontmatterFieldNames,
} from "./SettingsService";

const obsidianMoment = moment as unknown as typeof import("moment");
/**
Expand Down Expand Up @@ -157,30 +161,42 @@ export default class FoodTrackerSettingTab extends PluginSettingTab {

const descEl = detailsEl.createEl("p", {
cls: "food-tracker-settings-collapsible-desc setting-item-description",
text: "Customize the frontmatter field names used to store nutrition totals in daily notes.",
text: "Choose which nutrition totals to display in daily note properties and customize their field names.",
});
descEl.style.marginTop = "0.5em";
descEl.style.marginBottom = "1em";

const fieldConfigs: Array<{
key: keyof FrontmatterFieldNames;
key: keyof FrontmatterFieldNames & keyof EnabledFrontmatterFields;
name: string;
desc: string;
}> = [
{ key: "calories", name: "Calories field", desc: "Field name for total calories" },
{ key: "fats", name: "Fats field", desc: "Field name for total fats (g)" },
{ key: "saturated_fats", name: "Saturated fats field", desc: "Field name for saturated fats (g)" },
{ key: "protein", name: "Protein field", desc: "Field name for total protein (g)" },
{ key: "carbs", name: "Carbs field", desc: "Field name for total carbohydrates (g)" },
{ key: "fiber", name: "Fiber field", desc: "Field name for total fiber (g)" },
{ key: "sugar", name: "Sugar field", desc: "Field name for total sugar (g)" },
{ key: "sodium", name: "Sodium field", desc: "Field name for total sodium (mg)" },
{ key: "calories", name: "Calories", desc: "Total calories" },
{ key: "fats", name: "Fats", desc: "Total fats (g)" },
{ key: "saturated_fats", name: "Saturated fats", desc: "Saturated fats (g)" },
{ key: "protein", name: "Protein", desc: "Total protein (g)" },
{ key: "carbs", name: "Carbs", desc: "Total carbohydrates (g)" },
{ key: "fiber", name: "Fiber", desc: "Total fiber (g)" },
{ key: "sugar", name: "Sugar", desc: "Total sugar (g)" },
{ key: "sodium", name: "Sodium", desc: "Total sodium (mg)" },
];

for (const config of fieldConfigs) {
new Setting(detailsEl)
.setName(config.name)
.setDesc(config.desc)
.addToggle(toggle =>
toggle.setValue(this.plugin.settings.enabledFrontmatterFields[config.key]).onChange(async value => {
const enabledFields = this.plugin.settings.enabledFrontmatterFields;
enabledFields[config.key] = value;
this.plugin.settingsService.updateEnabledFrontmatterFields(enabledFields);
Comment on lines +190 to +192
Copy link

Copilot AI Jan 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code directly mutates this.plugin.settings.enabledFrontmatterFields by reference. Line 190 creates a reference to the object, and line 191 mutates it before passing to updateEnabledFrontmatterFields. This should create a new object instead to avoid unintended side effects. Change line 190-191 to pass a new object with just the changed field: this.plugin.settingsService.updateEnabledFrontmatterFields({ [config.key]: value })

Suggested change
const enabledFields = this.plugin.settings.enabledFrontmatterFields;
enabledFields[config.key] = value;
this.plugin.settingsService.updateEnabledFrontmatterFields(enabledFields);
this.plugin.settingsService.updateEnabledFrontmatterFields({ [config.key]: value });

Copilot uses AI. Check for mistakes.
this.plugin.settings = {
...this.plugin.settings,
enabledFrontmatterFields: this.plugin.settingsService.currentEnabledFrontmatterFields,
};
await this.plugin.saveSettings();
})
)
.addText(text =>
text
.setPlaceholder(DEFAULT_FRONTMATTER_FIELD_NAMES[config.key])
Expand Down
20 changes: 17 additions & 3 deletions src/FrontmatterTotalsService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import { App, TFile } from "obsidian";
import { NutrientData, calculateNutritionTotals } from "./NutritionCalculator";
import NutrientCache from "./NutrientCache";
import { DEFAULT_FRONTMATTER_FIELD_NAMES, FrontmatterFieldNames, SettingsService } from "./SettingsService";
import {
DEFAULT_ENABLED_FRONTMATTER_FIELDS,
DEFAULT_FRONTMATTER_FIELD_NAMES,
EnabledFrontmatterFields,
FrontmatterFieldNames,
SettingsService,
} from "./SettingsService";
import GoalsService from "./GoalsService";
import DailyNoteLocator from "./DailyNoteLocator";

Expand Down Expand Up @@ -69,12 +75,14 @@ export function nutrientDataToFrontmatterTotals(data: NutrientData): Frontmatter
export function applyNutrientTotalsToFrontmatter(
frontmatter: Record<string, unknown>,
totals: NutrientData | null,
fieldNames: FrontmatterFieldNames = DEFAULT_FRONTMATTER_FIELD_NAMES
fieldNames: FrontmatterFieldNames = DEFAULT_FRONTMATTER_FIELD_NAMES,
enabledFields: EnabledFrontmatterFields = DEFAULT_ENABLED_FRONTMATTER_FIELDS
): void {
const keys = Object.keys(fieldNames) as FrontmatterKey[];

if (!totals || Object.keys(totals).length === 0) {
for (const key of keys) {
if (!enabledFields[key]) continue;
const frontmatterKey = fieldNames[key];
if (frontmatterKey in frontmatter) {
frontmatter[frontmatterKey] = 0;
Expand All @@ -86,6 +94,7 @@ export function applyNutrientTotalsToFrontmatter(
const formattedTotals = nutrientDataToFrontmatterTotals(totals);

for (const key of keys) {
if (!enabledFields[key]) continue;
const frontmatterKey = fieldNames[key];
const value = formattedTotals[key];
if (value !== undefined) {
Expand Down Expand Up @@ -174,7 +183,12 @@ export default class FrontmatterTotalsService {
}

private updateFrontmatterValues(frontmatter: Record<string, unknown>, totals: NutrientData | null): void {
applyNutrientTotalsToFrontmatter(frontmatter, totals, this.settingsService.currentFrontmatterFieldNames);
applyNutrientTotalsToFrontmatter(
frontmatter,
totals,
this.settingsService.currentFrontmatterFieldNames,
this.settingsService.currentEnabledFrontmatterFields
);
}

cancelPendingUpdates(): void {
Expand Down
55 changes: 55 additions & 0 deletions src/SettingsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,28 @@ export interface FrontmatterFieldNames {
sodium: string;
}

export interface EnabledFrontmatterFields {
calories: boolean;
fats: boolean;
saturated_fats: boolean;
protein: boolean;
carbs: boolean;
fiber: boolean;
sugar: boolean;
sodium: boolean;
}

export const DEFAULT_ENABLED_FRONTMATTER_FIELDS: EnabledFrontmatterFields = Object.freeze({
calories: true,
fats: true,
saturated_fats: true,
protein: true,
carbs: true,
fiber: true,
sugar: true,
sodium: true,
});

const FRONTMATTER_KEYS_ORDER: Array<keyof FrontmatterFieldNames> = [
"calories",
"fats",
Expand All @@ -39,6 +61,10 @@ export function cloneFrontmatterFieldNames(names: FrontmatterFieldNames): Frontm
return { ...names };
}

export function cloneEnabledFrontmatterFields(fields: EnabledFrontmatterFields): EnabledFrontmatterFields {
return { ...fields };
}

function sanitizeFrontmatterFieldNames(
fieldNames: Partial<FrontmatterFieldNames>,
base: FrontmatterFieldNames = DEFAULT_FRONTMATTER_FIELD_NAMES
Expand Down Expand Up @@ -72,6 +98,8 @@ export function sanitizeSettings(settings: FoodTrackerPluginSettings): FoodTrack
return {
...settings,
frontmatterFieldNames: sanitizeFrontmatterFieldNames(settings.frontmatterFieldNames),
enabledFrontmatterFields:
settings.enabledFrontmatterFields ?? cloneEnabledFrontmatterFields(DEFAULT_ENABLED_FRONTMATTER_FIELDS),
};
}

Expand All @@ -84,6 +112,7 @@ export interface FoodTrackerPluginSettings {
showCalorieHints: boolean;
dailyNoteFormat: string;
frontmatterFieldNames: FrontmatterFieldNames;
enabledFrontmatterFields: EnabledFrontmatterFields;
linkType: "wikilink" | "markdown";
}

Expand All @@ -96,6 +125,7 @@ export const DEFAULT_SETTINGS: FoodTrackerPluginSettings = {
showCalorieHints: true,
dailyNoteFormat: "YYYY-MM-DD",
frontmatterFieldNames: cloneFrontmatterFieldNames(DEFAULT_FRONTMATTER_FIELD_NAMES),
enabledFrontmatterFields: cloneEnabledFrontmatterFields(DEFAULT_ENABLED_FRONTMATTER_FIELDS),
linkType: "wikilink",
};

Expand Down Expand Up @@ -183,6 +213,13 @@ export class SettingsService {
return this.settings$.pipe(map(settings => settings.frontmatterFieldNames));
}

/**
* Observable stream of the enabled frontmatter fields
*/
get enabledFrontmatterFields$(): Observable<EnabledFrontmatterFields> {
return this.settings$.pipe(map(settings => settings.enabledFrontmatterFields));
}

/**
* Get the current settings value synchronously
*/
Expand Down Expand Up @@ -260,6 +297,13 @@ export class SettingsService {
return cloneFrontmatterFieldNames(this.currentSettings.frontmatterFieldNames);
}

/**
* Get the current enabled frontmatter fields synchronously
*/
get currentEnabledFrontmatterFields(): EnabledFrontmatterFields {
return cloneEnabledFrontmatterFields(this.currentSettings.enabledFrontmatterFields);
}

/**
* Get the current link type value synchronously
*/
Expand Down Expand Up @@ -343,6 +387,17 @@ export class SettingsService {
this.updateSetting("frontmatterFieldNames", sanitized);
}

/**
* Updates enabled frontmatter fields (partial update supported)
*/
updateEnabledFrontmatterFields(fields: Partial<EnabledFrontmatterFields>): void {
const merged = {
...this.currentEnabledFrontmatterFields,
...fields,
};
this.updateSetting("enabledFrontmatterFields", merged);
}

/**
* Initialize the service with settings
*/
Expand Down