Compare commits

..

No commits in common. "main" and "1.4.1" have entirely different histories.
main ... 1.4.1

7 changed files with 12 additions and 170 deletions

View file

@ -34,7 +34,7 @@ Track time until important deadlines, events, or milestones with visual progress
## How to Use
Create a countdown progress bar by adding a code block with the `countdown-to` language identifier:
````
```countdown-to
title: Project Deadline
startDate: 2025-03-12
@ -48,7 +48,7 @@ infoFormat: {percent}% complete - {remaining} until {end:LLL d, yyyy}
updateInRealTime: true
updateIntervalInSeconds: 30
```
````
### Required Parameters
If no startDate and endDate is provided the countdown will assume the current day. So at the very least the widget requires a start time and end time. If that is the case the countdown will run daily.
@ -106,34 +106,6 @@ For example:
See the [Luxon formatting reference](https://moment.github.io/luxon/#/formatting?id=table-of-tokens) for all available format tokens.
### Using parameters
Countdown To is compatible with Obsidian Parameters. To use them you use the same parameters explained in #How to Use but have them prefixed with `countdown-`, for example:
```
---
countdown-startDate: 2025-08-31
countdown-startTime: 19:00
countdown-endDate: 2025-09-15
countdown-endTime: 19:00
countdown-title: Property Title Test
countdown-color: "#ff5722"
countdown-type: Line
countdown-trailColor: "#ff0022"
countdown-progressType: progress
countdown-updateInRealTime: true
countdown-updateIntervalInSeconds: 10
countdown-infoFormat: "{percent}% complete - {remaining} until {end:LLL d, yyyy}"
countdown-infoFormatUpcoming: Upcoming {title}
countdown-onCompleteText: Completed {title}
---
```
These parameters can be overriden in a `countdown-to` but they will now be the default value for the file and they don't need to be written in a `countdown-to` block.
````
```countdown-to
```
````
Will now work even with no `startDate`
## Configuration
You can configure default settings for all progress bars in the plugin settings:

View file

@ -1,7 +1,7 @@
{
"id": "countdown-to",
"name": "Countdown To",
"version": "1.6.0",
"version": "1.4.1",
"minAppVersion": "0.15.0",
"description": "Create countdown progress bars in your notes",
"author": "Gui Cattani",

4
package-lock.json generated
View file

@ -1707,8 +1707,8 @@
}
},
"node_modules/merge2": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.5.0.tgz",
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
"dev": true,
"license": "MIT",

View file

@ -1,6 +1,6 @@
{
"name": "obsidian-countdown-to",
"version": "1.5.0",
"version": "1.4.1",
"description": "Create countdown progress bars in your notes",
"main": "main.js",
"scripts": {

View file

@ -1,4 +1,4 @@
import { MarkdownRenderChild, MarkdownPostProcessorContext, TFile } from "obsidian";
import { MarkdownRenderChild } from "obsidian";
import CountdownToPlugin from "./main";
import { DateTime, Duration, Interval } from 'luxon';
@ -8,19 +8,16 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild {
plugin: CountdownToPlugin;
source: string;
id: string;
context: MarkdownPostProcessorContext;
constructor(
plugin: CountdownToPlugin,
source: string,
containerEl: HTMLElement,
id: string,
context: MarkdownPostProcessorContext
id: string
) {
super(containerEl);
this.plugin = plugin;
this.id = id;
this.source = source;
this.context = context;
this.display();
}
@ -78,18 +75,7 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild {
const infoEl = containerEl.createDiv({ cls: 'countdown-to-info' });
let bar;
const initialbarColor = params.color || this.plugin.settings.defaultBarColor;
const isGradient = (params.colorInGradient || this.plugin.settings.defaultColorInGradient.toString()) === 'true';
const progressType = params.progressType || this.plugin.settings.defaultProgressType;
let barColor: string;
if (isGradient) {
const startColorParam = params.startColor || this.plugin.settings.defaultStartColor;
const endColorParam = params.endColor || this.plugin.settings.defaultEndColor;
const isCountdown = progressType.toLowerCase() === 'countdown';
barColor = isCountdown ? endColorParam : startColorParam;
} else {
barColor = initialbarColor;
}
const barColor = params.color || this.plugin.settings.defaultBarColor;
const trailColor = params.trailColor || this.plugin.settings.defaultTrailColor;
const commonOptions = {
strokeWidth: 4,
@ -221,8 +207,6 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild {
} else {
countdownTo.bar.set(Math.floor(progress * 100) / 100);
}
this.updateBarGradient(countdownTo, progress, params, progressType);
if (progress >= 1) {
countdownTo.infoEl.setText(
@ -278,116 +262,10 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild {
}
}
private updateBarGradient(
countdownTo: any,
progress: number,
params: Record<string, string>,
progressType: string
): void {
const isGradient =
(params.colorInGradient || this.plugin.settings.defaultColorInGradient?.toString()) === "true";
if (!isGradient || !countdownTo?.bar) return;
const isCountdown = progressType.toLowerCase() === "countdown";
const startColor = params.startColor || this.plugin.settings.defaultStartColor;
const endColor = params.endColor || this.plugin.settings.defaultEndColor;
const midColorParam = params.midColor || this.plugin.settings.defaultMidColor;
const midColor = this.parseHexColor(midColorParam);
let colorProgress: number;
if (isCountdown) {
colorProgress = 1.0 - progress;
} else {
colorProgress = Math.floor(progress * 100) / 100;
}
let newColor: string;
if (midColor) {
if (colorProgress < 0.5) {
newColor = this.interpolateColor(startColor, midColorParam, colorProgress * 2);
} else {
newColor = this.interpolateColor(midColorParam, endColor, (colorProgress - 0.5) * 2);
}
} else {
newColor = this.interpolateColor(startColor, endColor, colorProgress);
}
const barPath = (countdownTo.bar as any)?.path as SVGPathElement;
if (barPath) barPath.setAttribute("stroke", newColor);
}
private interpolateColor(color1: string, color2: string, factor: number): string {
const c1 = this.parseHexColor(color1);
const c2 = this.parseHexColor(color2);
if (!c1 || !c2) return color1;
const result = c1.map((v, i) => Math.round(v + factor * (c2[i] - v)));
return `#${result.map((x) => x.toString(16).padStart(2, "0")).join("")}`;
}
private parseHexColor(hex: string): number[] | null {
if (!hex) return null;
const match = hex.trim().replace("#", "").match(/^([0-9a-f]{6})$/i);
if (!match) return null;
const intVal = parseInt(match[1], 16);
return [(intVal >> 16) & 255, (intVal >> 8) & 255, intVal & 255];
}
getPropertiesFromFrontmatter(): Record<string, string> {
const properties: Record<string, string> = {};
if (!this.context.sourcePath) {
return properties;
}
const file = this.plugin.app.vault.getAbstractFileByPath(this.context.sourcePath);
if (!file || !(file instanceof TFile)) {
return properties;
}
const fileCache = this.plugin.app.metadataCache.getFileCache(file);
if (!fileCache || !fileCache.frontmatter) {
return properties;
}
const frontmatter = fileCache.frontmatter;
const propertyMapping = [
'startDate',
'startTime',
'endDate',
'endTime',
'title',
'color',
'trailColor',
'type',
'progressType',
'updateInRealTime',
'updateInterval',
'updateIntervalInSeconds',
'infoFormat',
'infoFormatUpcoming',
'onCompleteText',
'colorInGradient',
'startColor',
'endColor',
'midColor'
];
propertyMapping.forEach(prop => {
const prefixedKey = `countdown-${prop}`;
if (frontmatter[prefixedKey] !== undefined) {
properties[prop] = String(frontmatter[prefixedKey]);
}
});
return properties;
}
parseCountdownToParams(source: string): Record<string, string> {
// Start with properties from frontmatter (obsidian metadata) and override with code block parameters
const params: Record<string, string> = this.getPropertiesFromFrontmatter();
const params: Record<string, string> = {};
const lines = source.trim().split('\n');
lines.forEach(line => {
const colonIndex = line.indexOf(':');
if (colonIndex !== -1) {

View file

@ -28,7 +28,7 @@ export default class CountdownToPlugin extends Plugin {
id = Math.random().toString(36).substring(2, 15);
}
ctx.addChild(new CountdownToMarkdownRenderChild(this, source, el, id, ctx));
ctx.addChild(new CountdownToMarkdownRenderChild(this, source, el, id));
});
this.addSettingTab(new CountdownToSettingTab(this.app, this));

View file

@ -13,10 +13,6 @@ export interface CountdownToSettings {
defaultInfoFormatUpcoming: string;
defaultUpdateInRealTime: boolean;
defaultUpdateIntervalSeconds: number;
defaultColorInGradient: boolean;
defaultStartColor: string;
defaultEndColor: string;
defaultMidColor: string;
}
export const DEFAULT_SETTINGS: CountdownToSettings = {
@ -30,10 +26,6 @@ export const DEFAULT_SETTINGS: CountdownToSettings = {
defaultInfoFormatUpcoming: '{title} is coming up in {remaining}!',
defaultUpdateInRealTime: false,
defaultUpdateIntervalSeconds: 1,
defaultColorInGradient: false,
defaultStartColor: '#ff5722',
defaultEndColor: '#4CAF50',
defaultMidColor: ' ',
};
export class CountdownToSettingTab extends PluginSettingTab {