Skip to content

Commit bdc2dc2

Browse files
authored
Merge pull request #160 from forketyfork/custom-metadata-field-names-e7a1
Add custom metadata field names
2 parents be43a70 + cc4ee8f commit bdc2dc2

File tree

9 files changed

+669
-221
lines changed

9 files changed

+669
-221
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Go to Settings > Food Tracker to configure:
222222
- `YYYY-MM-DD-[journal]``2025-11-12-journal`
223223
- `dddd YYYY-MM-DD``Wednesday 2025-11-12`
224224
- **Goals file**: Specify the path to your nutrition goals file (e.g., "nutrition-goals.md"). The field includes type-ahead file suggestions.
225+
- **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`).
225226

226227
> **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.
227228

src/FoodTrackerPlugin.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import FoodHighlightExtension from "./FoodHighlightExtension";
88
import FoodHighlightPostProcessor from "./FoodHighlightPostProcessor";
99
import GoalsHighlightExtension from "./GoalsHighlightExtension";
1010
import DocumentTotalManager from "./DocumentTotalManager";
11-
import { SettingsService, FoodTrackerPluginSettings, DEFAULT_SETTINGS } from "./SettingsService";
11+
import { SettingsService, FoodTrackerPluginSettings, DEFAULT_SETTINGS, sanitizeSettings } from "./SettingsService";
1212
import GoalsService from "./GoalsService";
1313
import { FOOD_TRACKER_ICON_NAME, FOOD_TRACKER_SVG_CONTENT } from "./icon";
1414
import StatisticsModal from "./StatisticsModal";
@@ -275,7 +275,13 @@ export default class FoodTrackerPlugin extends Plugin {
275275
totalDisplayMode: Platform.isMobile ? "document" : DEFAULT_SETTINGS.totalDisplayMode,
276276
} as FoodTrackerPluginSettings;
277277

278-
this.settings = Object.assign({}, mobileAwareDefaults, savedData);
278+
const merged = Object.assign({}, mobileAwareDefaults, savedData);
279+
merged.frontmatterFieldNames = {
280+
...DEFAULT_SETTINGS.frontmatterFieldNames,
281+
...(savedData.frontmatterFieldNames ?? {}),
282+
};
283+
284+
this.settings = sanitizeSettings(merged);
279285
}
280286

281287
async saveSettings() {

src/FoodTrackerSettingTab.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { App, PluginSettingTab, Setting, moment, normalizePath } from "obsidian"
22
import type FoodTrackerPlugin from "./FoodTrackerPlugin";
33
import FolderSuggest from "./FolderSuggest";
44
import FileSuggest from "./FileSuggest";
5+
import { DEFAULT_FRONTMATTER_FIELD_NAMES, type FrontmatterFieldNames } from "./SettingsService";
56

67
const obsidianMoment = moment as unknown as typeof import("moment");
78
/**
@@ -141,5 +142,60 @@ export default class FoodTrackerSettingTab extends PluginSettingTab {
141142
});
142143
new FileSuggest(this.app, text.inputEl);
143144
});
145+
146+
this.addFrontmatterFieldNamesSection(containerEl);
147+
}
148+
149+
private addFrontmatterFieldNamesSection(containerEl: HTMLElement): void {
150+
const detailsEl = containerEl.createEl("details", {
151+
cls: "food-tracker-settings-collapsible",
152+
});
153+
detailsEl.createEl("summary", {
154+
text: "Metadata field names",
155+
cls: "food-tracker-settings-collapsible-summary",
156+
});
157+
158+
const descEl = detailsEl.createEl("p", {
159+
cls: "food-tracker-settings-collapsible-desc setting-item-description",
160+
text: "Customize the frontmatter field names used to store nutrition totals in daily notes.",
161+
});
162+
descEl.style.marginTop = "0.5em";
163+
descEl.style.marginBottom = "1em";
164+
165+
const fieldConfigs: Array<{
166+
key: keyof FrontmatterFieldNames;
167+
name: string;
168+
desc: string;
169+
}> = [
170+
{ key: "calories", name: "Calories field", desc: "Field name for total calories" },
171+
{ key: "fats", name: "Fats field", desc: "Field name for total fats (g)" },
172+
{ key: "saturated_fats", name: "Saturated fats field", desc: "Field name for saturated fats (g)" },
173+
{ key: "protein", name: "Protein field", desc: "Field name for total protein (g)" },
174+
{ key: "carbs", name: "Carbs field", desc: "Field name for total carbohydrates (g)" },
175+
{ key: "fiber", name: "Fiber field", desc: "Field name for total fiber (g)" },
176+
{ key: "sugar", name: "Sugar field", desc: "Field name for total sugar (g)" },
177+
{ key: "sodium", name: "Sodium field", desc: "Field name for total sodium (mg)" },
178+
];
179+
180+
for (const config of fieldConfigs) {
181+
new Setting(detailsEl)
182+
.setName(config.name)
183+
.setDesc(config.desc)
184+
.addText(text =>
185+
text
186+
.setPlaceholder(DEFAULT_FRONTMATTER_FIELD_NAMES[config.key])
187+
.setValue(this.plugin.settings.frontmatterFieldNames[config.key])
188+
.onChange(async value => {
189+
const frontmatterFieldNames = this.plugin.settings.frontmatterFieldNames;
190+
frontmatterFieldNames[config.key] = value;
191+
this.plugin.settingsService.updateFrontmatterFieldNames(frontmatterFieldNames);
192+
this.plugin.settings = {
193+
...this.plugin.settings,
194+
frontmatterFieldNames: this.plugin.settingsService.currentFrontmatterFieldNames,
195+
};
196+
await this.plugin.saveSettings();
197+
})
198+
);
199+
}
144200
}
145201
}

src/FrontmatterTotalsService.ts

Lines changed: 21 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,14 @@
11
import { App, TFile } from "obsidian";
22
import { NutrientData, calculateNutritionTotals } from "./NutritionCalculator";
33
import NutrientCache from "./NutrientCache";
4-
import { SettingsService } from "./SettingsService";
4+
import { DEFAULT_FRONTMATTER_FIELD_NAMES, FrontmatterFieldNames, SettingsService } from "./SettingsService";
55
import GoalsService from "./GoalsService";
66
import DailyNoteLocator from "./DailyNoteLocator";
77

88
export const FRONTMATTER_PREFIX = "ft-";
9+
export const FRONTMATTER_KEYS = DEFAULT_FRONTMATTER_FIELD_NAMES;
910

10-
export const FRONTMATTER_KEYS = {
11-
calories: `${FRONTMATTER_PREFIX}calories`,
12-
fats: `${FRONTMATTER_PREFIX}fats`,
13-
saturated_fats: `${FRONTMATTER_PREFIX}saturated_fats`,
14-
protein: `${FRONTMATTER_PREFIX}protein`,
15-
carbs: `${FRONTMATTER_PREFIX}carbs`,
16-
fiber: `${FRONTMATTER_PREFIX}fiber`,
17-
sugar: `${FRONTMATTER_PREFIX}sugar`,
18-
sodium: `${FRONTMATTER_PREFIX}sodium`,
19-
} as const;
20-
21-
export type FrontmatterKey = keyof typeof FRONTMATTER_KEYS;
11+
export type FrontmatterKey = keyof FrontmatterFieldNames;
2212

2313
export interface FrontmatterTotals {
2414
calories?: number;
@@ -31,11 +21,16 @@ export interface FrontmatterTotals {
3121
sodium?: number;
3222
}
3323

34-
export function extractFrontmatterTotals(frontmatter: Record<string, unknown>): FrontmatterTotals | null {
24+
export function extractFrontmatterTotals(
25+
frontmatter: Record<string, unknown>,
26+
fieldNames: FrontmatterFieldNames = DEFAULT_FRONTMATTER_FIELD_NAMES
27+
): FrontmatterTotals | null {
3528
const totals: FrontmatterTotals = {};
3629
let hasAnyValue = false;
3730

38-
for (const [key, frontmatterKey] of Object.entries(FRONTMATTER_KEYS)) {
31+
const keys = Object.keys(fieldNames) as FrontmatterKey[];
32+
for (const key of keys) {
33+
const frontmatterKey = fieldNames[key];
3934
const value = frontmatter[frontmatterKey];
4035
if (value !== undefined && value !== null) {
4136
let numValue: number;
@@ -47,7 +42,7 @@ export function extractFrontmatterTotals(frontmatter: Record<string, unknown>):
4742
continue;
4843
}
4944
if (!isNaN(numValue)) {
50-
totals[key as FrontmatterKey] = numValue;
45+
totals[key] = numValue;
5146
hasAnyValue = true;
5247
}
5348
}
@@ -73,10 +68,14 @@ export function nutrientDataToFrontmatterTotals(data: NutrientData): Frontmatter
7368

7469
export function applyNutrientTotalsToFrontmatter(
7570
frontmatter: Record<string, unknown>,
76-
totals: NutrientData | null
71+
totals: NutrientData | null,
72+
fieldNames: FrontmatterFieldNames = DEFAULT_FRONTMATTER_FIELD_NAMES
7773
): void {
74+
const keys = Object.keys(fieldNames) as FrontmatterKey[];
75+
7876
if (!totals || Object.keys(totals).length === 0) {
79-
for (const frontmatterKey of Object.values(FRONTMATTER_KEYS)) {
77+
for (const key of keys) {
78+
const frontmatterKey = fieldNames[key];
8079
if (frontmatterKey in frontmatter) {
8180
frontmatter[frontmatterKey] = 0;
8281
}
@@ -86,8 +85,9 @@ export function applyNutrientTotalsToFrontmatter(
8685

8786
const formattedTotals = nutrientDataToFrontmatterTotals(totals);
8887

89-
for (const [key, frontmatterKey] of Object.entries(FRONTMATTER_KEYS)) {
90-
const value = formattedTotals[key as FrontmatterKey];
88+
for (const key of keys) {
89+
const frontmatterKey = fieldNames[key];
90+
const value = formattedTotals[key];
9191
if (value !== undefined) {
9292
if (value !== 0) {
9393
frontmatter[frontmatterKey] = value;
@@ -174,7 +174,7 @@ export default class FrontmatterTotalsService {
174174
}
175175

176176
private updateFrontmatterValues(frontmatter: Record<string, unknown>, totals: NutrientData | null): void {
177-
applyNutrientTotalsToFrontmatter(frontmatter, totals);
177+
applyNutrientTotalsToFrontmatter(frontmatter, totals, this.settingsService.currentFrontmatterFieldNames);
178178
}
179179

180180
cancelPendingUpdates(): void {

src/SettingsService.ts

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,79 @@ import { BehaviorSubject, Observable } from "rxjs";
22
import { map } from "rxjs/operators";
33
import { SPECIAL_CHARS_REGEX } from "./constants";
44

5+
export interface FrontmatterFieldNames {
6+
calories: string;
7+
fats: string;
8+
saturated_fats: string;
9+
protein: string;
10+
carbs: string;
11+
fiber: string;
12+
sugar: string;
13+
sodium: string;
14+
}
15+
16+
const FRONTMATTER_KEYS_ORDER: Array<keyof FrontmatterFieldNames> = [
17+
"calories",
18+
"fats",
19+
"saturated_fats",
20+
"protein",
21+
"carbs",
22+
"fiber",
23+
"sugar",
24+
"sodium",
25+
];
26+
27+
export const DEFAULT_FRONTMATTER_FIELD_NAMES: FrontmatterFieldNames = Object.freeze({
28+
calories: "ft-calories",
29+
fats: "ft-fats",
30+
saturated_fats: "ft-saturated_fats",
31+
protein: "ft-protein",
32+
carbs: "ft-carbs",
33+
fiber: "ft-fiber",
34+
sugar: "ft-sugar",
35+
sodium: "ft-sodium",
36+
});
37+
38+
export function cloneFrontmatterFieldNames(names: FrontmatterFieldNames): FrontmatterFieldNames {
39+
return { ...names };
40+
}
41+
42+
function sanitizeFrontmatterFieldNames(
43+
fieldNames: Partial<FrontmatterFieldNames>,
44+
base: FrontmatterFieldNames = DEFAULT_FRONTMATTER_FIELD_NAMES
45+
): FrontmatterFieldNames {
46+
const merged: FrontmatterFieldNames = {
47+
...base,
48+
...fieldNames,
49+
};
50+
51+
const sanitized: FrontmatterFieldNames = { ...merged } as FrontmatterFieldNames;
52+
const seen = new Set<string>();
53+
54+
for (const key of FRONTMATTER_KEYS_ORDER) {
55+
const rawValue = merged[key];
56+
const trimmed = (rawValue ?? "").trim();
57+
const value = trimmed || base[key];
58+
59+
if (seen.has(value)) {
60+
sanitized[key] = base[key];
61+
continue;
62+
}
63+
64+
sanitized[key] = value;
65+
seen.add(value);
66+
}
67+
68+
return sanitized;
69+
}
70+
71+
export function sanitizeSettings(settings: FoodTrackerPluginSettings): FoodTrackerPluginSettings {
72+
return {
73+
...settings,
74+
frontmatterFieldNames: sanitizeFrontmatterFieldNames(settings.frontmatterFieldNames),
75+
};
76+
}
77+
578
export interface FoodTrackerPluginSettings {
679
nutrientDirectory: string;
780
totalDisplayMode: "status-bar" | "document";
@@ -10,6 +83,7 @@ export interface FoodTrackerPluginSettings {
1083
goalsFile: string;
1184
showCalorieHints: boolean;
1285
dailyNoteFormat: string;
86+
frontmatterFieldNames: FrontmatterFieldNames;
1387
linkType: "wikilink" | "markdown";
1488
}
1589

@@ -21,6 +95,7 @@ export const DEFAULT_SETTINGS: FoodTrackerPluginSettings = {
2195
goalsFile: "nutrition-goals.md",
2296
showCalorieHints: true,
2397
dailyNoteFormat: "YYYY-MM-DD",
98+
frontmatterFieldNames: cloneFrontmatterFieldNames(DEFAULT_FRONTMATTER_FIELD_NAMES),
2499
linkType: "wikilink",
25100
};
26101

@@ -101,6 +176,13 @@ export class SettingsService {
101176
return this.settings$.pipe(map(settings => settings.showCalorieHints));
102177
}
103178

179+
/**
180+
* Observable stream of the frontmatter field names
181+
*/
182+
get frontmatterFieldNames$(): Observable<FrontmatterFieldNames> {
183+
return this.settings$.pipe(map(settings => settings.frontmatterFieldNames));
184+
}
185+
104186
/**
105187
* Get the current settings value synchronously
106188
*/
@@ -171,6 +253,13 @@ export class SettingsService {
171253
return this.currentSettings.showCalorieHints;
172254
}
173255

256+
/**
257+
* Get the current frontmatter field names synchronously
258+
*/
259+
get currentFrontmatterFieldNames(): FrontmatterFieldNames {
260+
return cloneFrontmatterFieldNames(this.currentSettings.frontmatterFieldNames);
261+
}
262+
174263
/**
175264
* Get the current link type value synchronously
176265
*/
@@ -182,7 +271,8 @@ export class SettingsService {
182271
* Updates all settings and notifies all subscribers
183272
*/
184273
updateSettings(newSettings: FoodTrackerPluginSettings): void {
185-
this.settingsSubject.next(newSettings);
274+
const sanitized = sanitizeSettings(newSettings);
275+
this.settingsSubject.next(sanitized);
186276
}
187277

188278
/**
@@ -245,6 +335,14 @@ export class SettingsService {
245335
this.updateSetting("showCalorieHints", show);
246336
}
247337

338+
/**
339+
* Updates frontmatter field names (partial update supported)
340+
*/
341+
updateFrontmatterFieldNames(fieldNames: Partial<FrontmatterFieldNames>): void {
342+
const sanitized = sanitizeFrontmatterFieldNames(fieldNames, this.currentFrontmatterFieldNames);
343+
this.updateSetting("frontmatterFieldNames", sanitized);
344+
}
345+
248346
/**
249347
* Initialize the service with settings
250348
*/

0 commit comments

Comments
 (0)