mirror of
https://github.com/guicattani/obsidian-countdown-to.git
synced 2026-07-22 05:42:39 +00:00
Compare commits
6 commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7ad7db0c5c | ||
|
|
95aeca4ecf | ||
|
|
d4c9364d4d | ||
|
|
10e73139d6 | ||
|
|
7981cbd273 | ||
|
|
1855612876 |
7 changed files with 171 additions and 13 deletions
32
README.md
32
README.md
|
|
@ -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,6 +106,34 @@ 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:
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
{
|
||||
"id": "countdown-to",
|
||||
"name": "Countdown To",
|
||||
"version": "1.4.1",
|
||||
"version": "1.6.0",
|
||||
"minAppVersion": "0.15.0",
|
||||
"description": "Create countdown progress bars in your notes",
|
||||
"author": "Gui Cattani",
|
||||
|
|
|
|||
4
package-lock.json
generated
4
package-lock.json
generated
|
|
@ -1707,8 +1707,8 @@
|
|||
}
|
||||
},
|
||||
"node_modules/merge2": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.5.0.tgz",
|
||||
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "obsidian-countdown-to",
|
||||
"version": "1.4.1",
|
||||
"version": "1.5.0",
|
||||
"description": "Create countdown progress bars in your notes",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { MarkdownRenderChild } from "obsidian";
|
||||
import { MarkdownRenderChild, MarkdownPostProcessorContext, TFile } from "obsidian";
|
||||
import CountdownToPlugin from "./main";
|
||||
|
||||
import { DateTime, Duration, Interval } from 'luxon';
|
||||
|
|
@ -8,16 +8,19 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild {
|
|||
plugin: CountdownToPlugin;
|
||||
source: string;
|
||||
id: string;
|
||||
context: MarkdownPostProcessorContext;
|
||||
constructor(
|
||||
plugin: CountdownToPlugin,
|
||||
source: string,
|
||||
containerEl: HTMLElement,
|
||||
id: string
|
||||
id: string,
|
||||
context: MarkdownPostProcessorContext
|
||||
) {
|
||||
super(containerEl);
|
||||
this.plugin = plugin;
|
||||
this.id = id;
|
||||
this.source = source;
|
||||
this.context = context;
|
||||
this.display();
|
||||
}
|
||||
|
||||
|
|
@ -75,7 +78,18 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild {
|
|||
const infoEl = containerEl.createDiv({ cls: 'countdown-to-info' });
|
||||
|
||||
let bar;
|
||||
const barColor = params.color || this.plugin.settings.defaultBarColor;
|
||||
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 trailColor = params.trailColor || this.plugin.settings.defaultTrailColor;
|
||||
const commonOptions = {
|
||||
strokeWidth: 4,
|
||||
|
|
@ -207,6 +221,8 @@ 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(
|
||||
|
|
@ -262,10 +278,116 @@ export class CountdownToMarkdownRenderChild extends MarkdownRenderChild {
|
|||
}
|
||||
}
|
||||
|
||||
parseCountdownToParams(source: string): Record<string, string> {
|
||||
const params: Record<string, string> = {};
|
||||
const lines = source.trim().split('\n');
|
||||
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 lines = source.trim().split('\n');
|
||||
lines.forEach(line => {
|
||||
const colonIndex = line.indexOf(':');
|
||||
if (colonIndex !== -1) {
|
||||
|
|
|
|||
|
|
@ -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.addChild(new CountdownToMarkdownRenderChild(this, source, el, id, ctx));
|
||||
});
|
||||
|
||||
this.addSettingTab(new CountdownToSettingTab(this.app, this));
|
||||
|
|
|
|||
|
|
@ -13,6 +13,10 @@ export interface CountdownToSettings {
|
|||
defaultInfoFormatUpcoming: string;
|
||||
defaultUpdateInRealTime: boolean;
|
||||
defaultUpdateIntervalSeconds: number;
|
||||
defaultColorInGradient: boolean;
|
||||
defaultStartColor: string;
|
||||
defaultEndColor: string;
|
||||
defaultMidColor: string;
|
||||
}
|
||||
|
||||
export const DEFAULT_SETTINGS: CountdownToSettings = {
|
||||
|
|
@ -26,6 +30,10 @@ 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 {
|
||||
|
|
|
|||
Loading…
Reference in a new issue