First of all, thanks for this fantastic piece of work leveraging yet more Datacore!
The following is just some advice for people who want to use your script:
Stylistic changes
1
I was fiddling around with it and have found that despite modifying the colors as per your guide, the cards remained white.
So I replaced all instances of 'white'
in your script with my choice of colour ('#1F1F1F'
).
Now, for other stylistic issues:
2
If the editor font size is too large (if you are zoomed out too much on PC and you set it too large), there are some misalignment issues here and there.
Quick fix:
Save and activate the following CSS snippet with any filename:
.markdown-preview-view.medium-font {
font-size: 16px;
}
Add to the frontmatter of the md file housing the script:
---
cssclasses:
- medium-font
---
3
I decided my dark mode was now more or less all right, but what about light mode?
Again, I applied a rather hackish quick fix:
Save and activate this CSS snippet:
.theme-light .habit-tracker-invert-colors {
filter: invert(90%) sepia(95%) contrast(90%);
}
- Users may want to adjust these numbers for a more seamless blend.
- EDIT: This was enough in Windows, but not on Linux, for some reason.
So your frontmatter must look like:
---
cssclasses:
- medium-font
- habit-tracker-invert-colors
---
Frontmatter key changes
Currently, nested properties are used, which practice is currently not supported by Obsidian (ver. 1.7.7 at the time of this post).
We can see that in the Properties view: we cannot manipulate the individual keys and values.
The community plugin Linter also cannot lint the file, and if one wants to add Meta Bind buttons to the daily note to update the property values, the prop keys will be inaccessible.
FIx with ChatGPT
These following changes seem to have done the trick:
To adjust the script logic for flat keys in the frontmatter, we will need to make the following changes:
Key Adjustments
- Change Frontmatter Key Format: Update the script logic to use flat keys like
habits-reading
, habits-run_duration
, etc., instead of nested properties.
- Modify Frontmatter Parsing: Adjust how the script reads and writes to frontmatter to account for the flat key format.
- Refactor Habit Update Logic: Ensure the habit tracking and duration updating logic works seamlessly with the flat key format.
Steps to Update the Script
1. Update getHabitStatus
Function
Modify how habit statuses are retrieved from frontmatter.
Old:
const getHabitStatus = (entry, habitId) => {
const habits = entry?.value('habits');
return habits?.[habitId] ?? false;
};
New:
const getHabitStatus = (entry, habitId) => {
const statusKey = \`habits-${habitId}\`;
return entry?.value(statusKey) ?? false;
};
2. Update getHabitDuration
Function
Refactor to fetch habit durations using flat keys.
Old:
const getHabitDuration = (entry, habitId) => {
const habits = entry?.value('habits');
return habits?.[\`${habitId}_duration\`] ?? null;
};
New:
const getHabitDuration = (entry, habitId) => {
const durationKey = \`habits-${habitId}_duration\`;
return entry?.value(durationKey) ?? null;
};
3. Update updateHabit
Function
Change the logic to update flat keys in the frontmatter.
Old:
async function updateHabit(entry, habitId) {
const file = app.vault.getAbstractFileByPath(entry.$path);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
if (!frontmatter.habits) frontmatter.habits = {};
const newStatus = !frontmatter.habits[habitId];
frontmatter.habits[habitId] = newStatus;
if (newStatus) {
const habit = HABITS.find(h => h.id === habitId);
frontmatter.habits[\`${habitId}_duration\`] = habit.defaultDuration;
}
});
}
New:
async function updateHabit(entry, habitId) {
const file = app.vault.getAbstractFileByPath(entry.$path);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
const statusKey = \`habits-${habitId}\`;
const durationKey = \`habits-${habitId}_duration\`;
const newStatus = !frontmatter[statusKey];
frontmatter[statusKey] = newStatus;
if (newStatus) {
const habit = HABITS.find(h => h.id === habitId);
frontmatter[durationKey] = habit.defaultDuration;
}
});
}
- Here users need to be mindful of adding the two-space indentations carefully.
4. Update updateHabitDuration
Function
Ensure durations are updated using the flat key format.
Old:
async function updateHabitDuration(entry, habitId, duration) {
const file = app.vault.getAbstractFileByPath(entry.$path);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
if (!frontmatter.habits) frontmatter.habits = {};
frontmatter.habits[\`${habitId}_duration\`] = parseInt(duration) || 0;
});
setEditingTime(null);
}
New:
async function updateHabitDuration(entry, habitId, duration) {
const file = app.vault.getAbstractFileByPath(entry.$path);
await app.fileManager.processFrontMatter(file, (frontmatter) => {
const durationKey = \`habits-${habitId}_duration\`;
frontmatter[durationKey] = parseInt(duration) || 0;
});
setEditingTime(null);
}
5. Modify calculateCompletedHabits
Function
Adjust the logic to count habits using the flat key format.
Old:
const calculateCompletedHabits = (entry) => {
if (!entry) return 0;
return HABITS.reduce((count, habit) =>
count + (getHabitStatus(entry, habit.id) ? 1 : 0), 0);
};
New:
const calculateCompletedHabits = (entry) => {
if (!entry) return 0;
return HABITS.reduce((count, habit) => {
const statusKey = \`habits-${habit.id}\`;
return count + (entry?.value(statusKey) ? 1 : 0);
}, 0);
};
Daily note setup
Which means users have to add the following to their daily notes:
habits-reading: false
habits-meditation: false
habits-workout: false
habits-writing: false
habits-run: false
habits-sleep: false
habits-reading_duration:
habits-meditation_duration:
habits-workout_duration:
habits-writing_duration:
habits-run_duration:
habits-sleep_duration:
- Here, apart from weightlifting, I was using the original habit types but once the user sets their own habit types as per the guide, the logic changes made above will still stick. You just need to add your keys with the
habits
+ dash (-
) + habit type fashion. Duration key additions here are optional as these keys are automatically added by manipulating the output. I just added them so they are in proper order and don’t need care about linting the file later on.
Meta Bind plugin use
So now in the properties pane, users can have access to each and every property to update the values if they want to alter from default values.
If they want, they can add to the daily note Meta Bind buttons (again, I was using a more general workout prop key name, and I didn’t bother to align default values from the script so they are all zero here):
**Reading:** `INPUT[inlineSelect(option(true), option(false)):habits-reading]`
**Reading Duration:** `INPUT[number(defaultValue(0)):habits-reading_duration]`
**Writing:** `INPUT[inlineSelect(option(true), option(false)):habits-writing]`
**Writing Duration:** `INPUT[number(defaultValue(0)):habits-writing_duration]`
**Meditation:** `INPUT[inlineSelect(option(true), option(false)):habits-meditation]`
**Meditation Duration:** `INPUT[number(defaultValue(0)):habits-meditation_duration]`
**Workout:** `INPUT[inlineSelect(option(true), option(false)):habits-workout]`
**Workout Duration:** `INPUT[number(defaultValue(0)):habits-workout_duration]`
**Run:** `INPUT[inlineSelect(option(true), option(false)):habits-run]`
**Run Duration:** `INPUT[number(defaultValue(0)):habits-run_duration]`
**Sleep:** `INPUT[inlineSelect(option(true), option(false)):habits-sleep]`
**Sleep Duration:** `INPUT[number(defaultValue(0)):habits-sleep_duration]`
Of course, these lines are quite too much in each and every daily note, so maybe it’s better to use the Properties pane on a sidebar to add whatever values one wants at the end of the day.
These buttons would make sense more if on daily note creation (in the morning?) one created a projection to what one wants to achieve that day.
Maybe someone can come up with a CSS to make these lines more compact (Obsidian columns plugin don’t play well with Meta Bind, unfortunately).
Edit:
The part with const StyledCard = ({ children, extraStyles = {} }) => (
also needed some tweaking as backgroundColor: 'var(--background-primary)'
might not be a good fit for all themes.